程序的栈结构

1 程序的地址空间布局

  一个程序在内存中运行,它靠四个东西:代码、栈、堆、数据段。代码段主要存放的就是可执行文件中的代码;数据段存放的就是程序中全局变量和静态变量;堆中是程序的动态内存区域,当程序使用malloc或new得到的内存是来自堆的;栈中维护的是函数调用的上下文,离开了栈就不可能实现函数的调用。在linux中它们的地址空间分布如下:

  其中最让我迷惑的还是栈,它是怎么保存程序执行的上下文的?我对它的理解还是保留在数据结构学的栈,什么先进先出,只对栈顶进行操作,对于它的具体应用还真是不太了解。以前写代码就很好奇,当调用一个程序时,栈中到底保留了些什么东西?今天终于有了点理解。

2 堆栈帧

  堆栈帧也叫活动记录,保存的是一个函数调用所需要维护的所有信息。它主要包含三个内容:

  • 函数的返回地址和参数
  • 临时变量:包括函数的非静态局部变量以及编译器自动生成的其它临时变量
  • 保存的上下文:包括在函数调用前后需要保存不变的寄存器值

  在i386中,一个函数的活动记录用ebp和esp这两个寄存器来划定范围。esp始终指向栈的顶部,同时也指向当前活动记录的顶部。相对的,ebp指向函数活动记录的一个固定位置,ebp又被称为帧指针。一个很常见的活动记录如下:

  参数及参数之后的数据是当前的活动记录,ebp固定在上图的位置,不随函数的执行而改变,相反地,esp始终指向栈顶,因此随着函数的执行,它总是变化的。ebp+4是这个函数的返回地址,ebp+8、ebp+12等是这个函数的参数。ebp指向的是调用该函数前ebp的值,这样在函数返回的时候,ebp就可以通过这个恢复到调用前的值。ebp下面的值是要保存的寄存器的值和函数中的局部变量,当然也可以不保存ebp的值,不过这样会减慢帧上寻址速度和无法准确定位函数的调用轨迹。之所以会形成这样的活动记录,是因为一个i386中函数总是这样调用的:

  • 把所有或者部分参数压入栈中
  • 把当前call调用指令的下一条指令地址压入栈中
  • 跳转到call调用的函数去执行

  后面两步由call指令自动完成,i386函数体的开头一般是这样的:

  • push ebp;保存调用前的ebp值
  • push ebp,esp;ebp指向栈中保存调用前ebp值的位置
  • sub esp,xxx;在栈上分配xxx字节的空间,这个是可选的
  • push xxx;保存xxx寄存器的值,这个也是可选的

  在函数返回时,一般是这样的:

  • pop xxx;恢复寄存器的值;如果开头有push xxx
  • mov esp,ebp;恢复esp,同时回收局部变量的空间
  • pop ebp;恢复调用前ebp的值
  • mov eax,xxx;如果函数有返回值,那么返回值一般放在eax中
  • ret;从栈中取回返回地址,并跳转到该位置,栈中参数空间的回收和调用惯例有个,看以参考http://www.cnblogs.com/chengxuyuancc/archive/2013/05/28/3103956.html

  第2和3条指令可以用leave指令代替  

  为了加深印象,下面反汇编下面一个函数看看:

int foo(int a)
{
    int b;

    b = a * 100;
    return b;
}


int main ( int argc, char *argv[] )
{
    foo(2);
    return 0;
}               /* ----------  end of function main  ---------- */

  反汇编代码:

  可以明显的看到foo和main函数中的代码和前面我们给出的函数开始和结束的一般代码是一样的。在main函数中调用foo前先是用movl指令把参数放入栈顶。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个使用结构实现四则运算的C语言程序: ``` #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 typedef struct { int top; float data[MAX_SIZE]; } Stack; void init(Stack *s) { s->top = -1; } int is_empty(Stack *s) { return s->top == -1; } int is_full(Stack *s) { return s->top == MAX_SIZE - 1; } void push(Stack *s, float x) { if (is_full(s)) { printf("Stack is full\n"); exit(1); } s->top++; s->data[s->top] = x; } float pop(Stack *s) { if (is_empty(s)) { printf("Stack is empty\n"); exit(1); } float x = s->data[s->top]; s->top--; return x; } float peek(Stack *s) { if (is_empty(s)) { printf("Stack is empty\n"); exit(1); } return s->data[s->top]; } int is_op(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } int precedence(char op) { if (op == '+' || op == '-') { return 1; } else if (op == '*' || op == '/') { return 2; } else { return 0; } } float eval(char *expr) { Stack num_stack; Stack op_stack; init(&num_stack); init(&op_stack); int i = 0; while (expr[i] != '\0') { if (isdigit(expr[i])) { float num = 0; while (isdigit(expr[i])) { num = num * 10 + (expr[i] - '0'); i++; } push(&num_stack, num); } else if (is_op(expr[i])) { while (!is_empty(&op_stack) && precedence(peek(&op_stack)) >= precedence(expr[i])) { float num2 = pop(&num_stack); float num1 = pop(&num_stack); char op = pop(&op_stack); float result; switch (op) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': result = num1 / num2; break; } push(&num_stack, result); } push(&op_stack, expr[i]); i++; } else { i++; } } while (!is_empty(&op_stack)) { float num2 = pop(&num_stack); float num1 = pop(&num_stack); char op = pop(&op_stack); float result; switch (op) { case '+': result = num1 + num2; break; case '-': result = num1 - num2; break; case '*': result = num1 * num2; break; case '/': result = num1 / num2; break; } push(&num_stack, result); } return pop(&num_stack); } int main() { char expr[] = "3+4*5-2/3"; float result = eval(expr); printf("%s = %.2f\n", expr, result); return 0; } ``` 该程序使用两个,一个用于存储数字,另一个用于存储运算符。它可以计算带有加减乘除运算符的表达式。该程序中还实现了的基本操作,如初始化、判断是否为空、判断是否已满、入、出、查看顶元素等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值