函数帧和可变参数和内存对齐

来源:互联网 发布:优化路政许可服务 编辑:程序博客网 时间:2024/03/29 14:47

在堆栈里面,一个函数所占的栈空间称为函数帧。在函数调用时,先将函数调用的下一条指令地址入栈(即函数调用语句的下面一个语句编译产生的第一条指令),然后将函数的各个参数依右向左的顺序入栈,当然,参数入栈的顺序是与函数调用约定相关的,常用的有:

__cdecl,即C调用约定,参数从右到左入栈,函数本身不清理栈,由调用者自行清理。

__stdcall,即pascal调用约定,它的参数也是从右到左入栈,函数本身清理栈。

__thiscall,它是C++里面专用的调用约定,即它自动为函数增加了一个this指针,也是从右向左入栈,this指针在最后一个入栈。

接着将函数中的局部变量入栈,静态变量不入栈,它存放在静态区(初始化数据段或BBS)。函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。这里要特别注意:函数的返回值的出栈不是像函数里面写的那样作为第一个出栈,实际上,函数的返回值是作为函数的引用参数来实现的。所有的函数其实不真正地具备向外界抛出一个值的能力,它只是很普通的将某值保存在某个寄存器并由caller取回的操作。

我们再来考虑C语言里面可变参数的实现。形如:void printf(const char*,...);后面三个省略号表示printf这个函数能接受不定数个参数。很明显,如果一个函数存在不定个参数,往这个函数传递不定个参数不是难点,因为只需要往函数栈里面扔进参数即可。语法层面只需要随便定义什么规则让编译器识别不定参数即可。我们只需要关注,函数内部如何使用这些参数。很容易想到的是,我们可以借用__cdecl这个调用约定来实现可变参数。这个调用约定没有强制要求知道参数的个数。该调用约定从最后一个参数开始反向入栈的,而且,它必须由调用者清理函数栈。

如函数:int sum(int a,int b,int c)在函数栈中的相对位置为:




__cdecl和__stdcall都是从右向左入栈,但前者由调用者清理函数线,后者由函数本身清理栈,这是因为,__cdecl规定函数内部并不知道参数有多少个,所以它并不能在函数内部清理栈,而只能由调用者清理栈(调用者肯定知道参数的个数),如上例中,调用者就可以使用 esp = esp+3*sizeof(int)来清理栈。而__stdcall要求参数个数必须是明确的,所以它可以在函数内部清理栈保持栈的平衡,pop a; pop b; pop c。

在__cdecl中,如果我们知道最下面一个参数的地址和每个参数的大小,只需要将前者作为基准地址,加上前者大小就可以得知下一个参数的地址,于是也就可以在函数内部任意使用了。所以,__cdecl给出要求,函数必须要知道一个确定地址的参数,即可变参数的函数至少有一个确定地址的参数,该确定参数前面入栈的参数(即在函数调用中,写在个确定参数后面的参数)都可以被寻址。__cdecl规定,写在最后面的一个参数,其下一个字节上的值为0,所以我们就可以界定参数的起始地址和结束地址,再根据每个参数所占的大小即可寻址每个参数。


下面关于内存对齐的数理推理是引自http://www.360doc.com/content/12/0804/11/3725126_228273988.shtml的。

对于两个正整数 x, n 总存在整数 q, r 使得x = nq + r, 其中  0<= r <n   //最小非负剩余


q, r 是唯一确定的。q = [x/n], r = x - n[x/n]. 这个是带余除法的一个简单形式。在 c 语言中, q, r 容易计算出来: q = x/n, r = x % n.
所谓把 x 按 n 对齐指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 这也相当于把 x 表示为:
x = nq + r', 其中 -n < r' <=0                //最大非正剩余   
nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理:
x+n = qn + (n+r'),其中 0<n+r'<=n            //最大非正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 <n    //最小非负剩余
所以 qn = [(x+n-1)/n]n. 用 c 语言计算就是:
((x+n-1)/n)*n
若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。得到:
(x+n-1) & (~(n-1))。

有了这个公式,就可以计算任意一个不定参数的地址了,再由函数里面不定参数的类型,就可以对应该类型去解析数据了。

代码及解释见下面的代码和注释。


回到





原创粉丝点击