大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接call指令)处继续执行。函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。
创新互联公司专业为企业提供尤溪网站建设、尤溪做网站、尤溪网站设计、尤溪网站制作等企业网站建设、网页设计与制作、尤溪企业网站模板建站服务,十余年尤溪做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
不同处理器和编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。
寄存器是处理器加工数据或运行程序的重要载体,用于存放程序执行中用到的数据和指令。因此函数调用栈的实现与处理器寄存器组密切相关。
AX(AH、AL):累加器。有些指令约定以AX(或AL)为源或目的寄存器。输入/输出指令必须通过AX或AL实现,例如:端口地址为43H的内容读入CPU的指令为INAL,43H或INAX,43H。目的操作数只能是AL/AX,而不能是其他的寄存器。 [5]
BX(BH、BL): 基址寄存器 。BX可用作间接寻址的地址寄存器和 基地址寄存器 ,BH、BL可用作8位通用数据寄存器。 [5]
CX(CH、CL):计数寄存器。CX在循环和串操作中充当计数器,指令执行后CX内容自动修改,因此称为计数寄存器。 [5]
DX(DH、DL):数据寄存器。除用作通用寄存器外,在 I/O指令 中可用作端口 地址寄存器 ,乘除指令中用作辅助累加器。 [5]
2.指针和 变址寄存器
BP( Base Pointer Register):基址指针寄存器。 [5]
SP( Stack Pointer Register): 堆栈指针寄存器 。 [5]
SI( Source Index Register):源变址寄存器。 [5]
DI( Destination Index Register):目的变址寄存器。 [5]
函数调用栈的典型内存布局如下图所示:
图中给出主调函数(caller)和被调函数(callee)的栈帧布局,"m(%ebp)"表示以EBP为基地址、偏移量为m字节的内存空间(中的内容)。该图基于两个假设:第一,函数返回值不是结构体或联合体,否则第一个参数将位于"12(%ebp)" 处;第二,每个参数都是4字节大小(栈的粒度为4字节)。在本文后续章节将就参数的传递和大小问题做进一步的探讨。 此外,函数可以没有参数和局部变量,故图中“Argument(参数)”和“Local Variable(局部变量)”不是函数栈帧结构的必需部分。
其中,主调函数将参数按照调用约定依次入栈(图中为从右到左),然后将指令指针EIP入栈以保存主调函数的返回地址(下一条待执行指令的地址)。进入被调函数时,被调函数将主调函数的帧基指针EBP入栈,并将主调函数的栈顶指针ESP值赋给被调函数的EBP(作为被调函数的栈底),接着改变ESP值来为函数局部变量预留空间。此时被调函数帧基指针指向被调函数的栈底。以该地址为基准,向上(栈底方向)可获取主调函数的返回地址、参数值,向下(栈顶方向)能获取被调函数的局部变量值,而该地址处又存放着上一层主调函数的帧基指针值。本级调用结束后,将EBP指针值赋给ESP,使ESP再次指向被调函数栈底以释放局部变量;再将已压栈的主调函数帧基指针弹出到EBP,并弹出返回地址到EIP。ESP继续上移越过参数,最终回到函数调用前的状态,即恢复原来主调函数的栈帧。如此递归便形成函数调用栈。
EBP指针在当前函数运行过程中(未调用其他函数时)保持不变。在函数调用前,ESP指针指向栈顶地址,也是栈底地址。在函数完成现场保护之类的初始化工作后,ESP会始终指向当前函数栈帧的栈顶,此时,若
方法很简单:你讲两个栈都传进去,类似于 : int pus(SeqStack1 *s, DataType* x1, SeqStack2 *s, DataType* x2)(SeqStack1是你的第一个栈,SeqStack2是第二个栈,DataType是你需要传进去的数据的类型),然后在这个函数里面先判断x1 ,x2是否为空,如果为空,则他对应的那个栈不需要压栈,如果不为空,则执行相应的压栈操作。
不明白继续追问!
写了一个链式栈,你看看
# include stdio.h
# include malloc.h
# include stdlib.h
typedef struct Node
{
int data;
struct Node *pNext;
}NODE, *PNODE;
typedef struct Stack
{
PNODE pTop;
PNODE pBottom;//pBottem是指向栈底下一个没有实际意义的元素
}STACK, *PSTACK;
void init( PSTACK );
void push( PSTACK, int );
void traverse( PSTACK );
int pop( PSTACK, int * );
int empty( PSTACK pS );
int main( void )
{
STACK S;//STACK等价于struct Stack
int val;
init( S );//目的是造出一个空栈
push( S, 1 );//压栈
push( S, 2 );
push( S, 3 );
push( S, 4 );
push( S, 5 );
push( S, 6 );
push( S, 7 );
traverse( S );//遍历输出
// clear( S ); //清空数据
// traverse( S );//遍历输出
if( pop( S, val ) )
{
printf( "出栈成功,出栈的元素是%d\n", val );
}
else
{
printf( "出栈失败" );
}
traverse( S );//遍历输出出栈之后的元素
return 0;
}
void init( PSTACK pS )
{
pS-pTop = ( PNODE )malloc( sizeof( NODE ) );
if( NULL == pS-pTop )
{
printf( "动态内存分配失败!\n" );
exit( -1 );
}
else
{
pS-pBottom = pS-pTop;
pS-pTop-pNext = NULL;//或是pS-pBottom = NULL;
}
}
void push( PSTACK pS, int val )
{
PNODE pNew = ( PNODE )malloc( sizeof( NODE ) );
pNew-data = val;
pNew-pNext = pS-pTop;//pS-Top不能改为pS-pBottom
pS-pTop = pNew;
}
void traverse( PSTACK pS )
{
PNODE p = pS-pTop;
while( p != pS-pBottom )
{
printf( "%d ", p-data );
p = p-pNext;
}
printf( "\n" );
}
int empty( PSTACK pS )
{
if( pS-pTop == pS-pBottom )
return 1;
else
return 0;
}
//把pS所指向的栈出栈一次,并把出栈的元素存入pVal形参所指向的变量中,如果出栈失败,则返回false,否则true
int pop( PSTACK pS, int *pVal)
{
if( empty( pS ) )//pS本身存放的就是S的地址
{
return 0;
}
else
{
PNODE r = pS-pTop;
*pVal = r-data;
pS-pTop = r-pNext;
free( r );
r = NULL; //为什么要把r赋给NULL呢??
return 1;
}
}
//clear清空
void clear( PSTACK pS )
{
if( empty( pS ) )
{
return ;
}
else
{
PNODE p = pS-pTop;
PNODE q = p-pNext;
while( p != pS-pBottom )
{
q = p-pNext;
free( p );
p = q;
}
pS-pTop = pS-pBottom;
}
}
Status StackTraverse(SqStack S, Status (*pFun)(ElemType))
{
while(S.bottom != S.top)
{
pFun(*--S.top);
}
return OK;
}
//visit函数
Status Visit(ElemType e)
{
printf("%d\n", e);
return OK;
}
//调用
StackTraverse(S, Visit);
#include"stdio.h"
#include"malloc.h"
#include"stdlib.h"
#define OK 1
#define ERROR 0
#define OVERFLOW -2
#define TRUE 1
#define FALSE 0
typedef char ElemType;
typedef int Status;
#define STACK_INIT_SIZE 100 //存储空间初始分配量
#define STACKINCREMENT 10 //存储空间分配增量
typedef struct
{
ElemType *base; //在栈构造和销毁之后,base的值为NULL
ElemType *top; //栈顶指针
int stacksize; //当前已分配的存储空间,以元素为单位
}SqStack;
// 构造一个空栈S
Status InitStack(SqStack S)
{
S.base=(ElemType *)malloc(STACK_INIT_SIZE*sizeof(ElemType));
if(!S.base) //存储分配失败
exit (OVERFLOW);
S.top=S.base;
S.stacksize=STACK_INIT_SIZE;
return OK;
}//InitStack
/////////////////// 若栈不空,则用e返回S的栈顶元素,并返回OK;否则返回ERROR /////
Status GetTop(SqStack S,ElemType e)
{
if(S.top==S.base)
return ERROR;
e=*(S.top-1);
return OK;
}//GetTop
////////////////// 插入元素e为新的栈顶元素 /////////////////////////////////////
Status Push(SqStack S,ElemType e)
{
if(S.top-S.base=S.stacksize) //栈满,追加存储空间
{
S.base=(ElemType *)realloc(S.base,(S.stacksize+STACKINCREMENT) * sizeof(ElemType));
if(!S.base)
exit (OVERFLOW);
S.top=S.base+S.stacksize;
S.stacksize+=STACKINCREMENT;
}
*S.top++=e;
return OK;
}//Push
////////////////// 若栈不空,则删除S的栈顶元素,用e返回其值,并返回OK;否则返回ERROR
Status Pop(SqStack S,ElemType e)
{
if(S.top==S.base)
return ERROR;
e=*--S.top;
return OK;
}//Pop
////////// main() //////////////////////////////
void main()
{
int i;
char ch,e,c;
SqStack S;
InitStack(S);
printf("1.Push\t2.Pop\t3.GetTop\t4.exit\n");
while(1)
{
printf("请选择:");
scanf("%d",i);
c=getchar(); //*****接受回车符******
switch (i)
{
case 1:
printf("请输入要插入的元素:");
scanf("%c",ch);
Push(S,ch);
break;
case 2:
printf("弹出栈顶元素:");
Pop(S,e);
printf("%c\n",e);
break;
case 3:
printf("取栈顶元素:");
GetTop(S,e);
printf("%c\n",e);
break;
case 4:
exit(0);
default:
printf("ERROR!Please Reput A Number\n");
}
}
}
其实想知道这些东西,单从C代码上是看不出什么来的。反汇编后看汇编代码一清二楚。当一个父函数调用子函数时,在父函数中先将子函数用到的参数压入堆栈,然后再以一个call指令调用子函数。而call指令其实要做两件事:将自己的EIP值压入堆栈;以一个jmp跳转到子函数代码的开始位置。而在子函数内,首先就是这样:
MOV EBP,ESP
SUB ESP,0x100
... ...
也就是要将当前ESP保存到EBP,然后用一个SUB指令开辟子函数所用的局部内存空间。子函数对父函数的参数的引用一般都是EBP+4,EBP+8这样的,这样刚好就能访问父函数压入堆栈的参数。而对自己申明的参数的访问,就是EBP-0,EBP-4等等。对汇编不太熟悉的只从C代码的逻辑上去分析这样的问题,而实际情况是C代码要转换成机器代码,机器代码做的一些工作只有在汇编中能看到。堆栈就是一个传递参数的内存块,编译器在编译代码时,自动生成了子函数访问父函数参数的代码。
不知道这样说能不能帮你解决问题。