C语言函数参数入栈的汇编理解
先来看这样一段程序:
#include#include #include void print1(int a,int b,int c){ printf(%p,&a); printf(%p,&b); printf(%p,&c);}int main(void){ print1(1,2,3); exit(0);}
它的输出是:
0022FF400022FF440022FF48发现a,b,c的地址是逐渐增大的,差值是4个字节。这和我所知道的:C函数参数入栈的顺序是从右到左 是相匹配的,而且地址的增大值也
与变量所占的字节数相匹配。
不过当把程序稍微做一下修改,如下:
#include再观察一下它的输出:#include #include void print2(char a,char b,char c){ printf(%p,&a); printf(%p,&b); printf(%p,&c);}int main(void){ print2(1,2,3); exit(0);}
0022FF2C0022FF280022FF24怎么和上面的效果是相反的!虽然我知道这肯定编译器的一个技巧,不过 参数入栈的顺序是从右到左的 概念却动摇了。
为了弄清楚其中的道理,必须观察程序生成的中间.s文件,为此,我执行了以下一条命令:
gcc -S test.c(当前C文件中保存的程序是文章一开始的那个) 在当前目录下生成test.s文件使用vim打开test.s文件(只截取主要内容了):
esp是指向栈顶的指针,ebp是用来备份这个指针的。栈的形状如下:
esp
ebp
|____________________________________________________
栈的最大值 栈的最小值
每压入一个参数入栈,就执行 esp = esp - sizoeof(参数)。不过在esp值变之前,先备份一下 ebp = esp,这样不管最后esp指到哪里去了,函数结束时就用这个ebp就能顺利回到调用者了。
print1: pushl %ebp//6.先把ebp压栈,保存这个指针 movl %esp, %ebp//7.使ebp这个指针保存着esp这个指针指向的地址值 subl $8, %esp//8.使esp - 8,也就是说空下8个字节以便实现某个功能 leal 8(%ebp), %eax//9.把(ebp + 8)的地址给eax 这个地方为什么要+8 因为这个函数在经历第5,6步的时候存在着压了两个4字节入栈的操作。此时+8就指向了实参1 movl %eax, 4(%esp)//10.这个时候就用到第8步空下来的8个字节中的4个了,原来是保存值,原理就是用C语言写两个数交换值时的那个第三个变量,即缓冲区 movl $.LC0, (%esp) //11.把字符串“%p”压栈 从第10,11步来看,两个参数的入栈顺序,其实不管顺序了,两个参数,最右边的在高地址,最左边的在低地址 call printf//12.调用函数printf,又是压栈出栈的操作了 到此可以得到8个字节的缓冲区全部用完了 leal 12(%ebp), %eax//13.同第9步,此时获取的是实参2的地址 movl %eax, 4(%esp)//14.我想说同上 movl $.LC0, (%esp) //15.我想说同上 call printf//16.我想说同上 leal 16(%ebp), %eax//17.同第9步,此时获取的是实参3的地址 movl %eax, 4(%esp)//18.我想说同上 movl $.LC0, (%esp) //19.我想说同上 call printf//20.我想说同上。到了此处我们就知道,printf打印参数的地址,这个地址是在main函数中压栈时分配的,是什么就是什么,符合参数入栈的顺序是从右到左这个说法。 leave ret .size print1, .-print1.globl main .type main, @functionmain: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $20, %esp//1.先把栈预留20个字节,这其中的原因(为什么是20)估计与什么算法有关 movl $3, 8(%esp)//2.看!,先把3放入esp + 8 movl $2, 4(%esp)//3.再看!,把2放入esp + 4 movl $1, (%esp)//4.最后把1放入esp call print1//5.调用函数print1。至此可以看到参数1,2,3是从右往左压入栈的,3在最高最地址(相对于1的保存地址来说),而1就在最低地址了(相对于3的保存地址来说) movl $0, (%esp) call exit .size main, .-main
好的,这个程序分析完了,再来看有疑问的程序吧:
print2: pushl %ebp//5.我想说同上 movl %esp, %ebp//6.我想说同上 subl $24, %esp//7.这个就不同上了,比上面那个esp - 8大很多吗,不过要记住,这24个字节是个缓冲区 movl 8(%ebp), %eax//8.把实参1放入eax movl 12(%ebp), %edx//9.把实参2放入edx movl 16(%ebp), %ecx//10.把实参3放入ecx movb %al, -4(%ebp)//11.把eax的低字节放入ebp - 4 movb %dl, -8(%ebp)//12.把edx的低字节放入ebp - 8 movb %cl, -12(%ebp)//13.把ecx的低字节放入ebp -12。从这个地方就可以发现问题的出现原因了,此时的实参1所存放的地址高于存放实参3的地址。到此,24字节的缓冲区已经使用了12 leal -4(%ebp), %eax//14.把实参1存放的地址放入eax movl %eax, 4(%esp)//15.把实参1放入esp + 4 movl $.LC0, (%esp) //16.把字符串“%p”放入esp call printf//17.调用函数printf。从此依然可以看出函数参数的入栈地址是最右边的在高地址,最左边的在低地址。到此24字节的缓冲区使用了20个,还余下4个没有用 leal -8(%ebp), %eax//18.我想说同上 movl %eax, 4(%esp)//19.我想说同上 movl $.LC0, (%esp) //20.我想说同上 call printf//21.我想说同上 leal -12(%ebp), %eax//22.我想说同上 movl %eax, 4(%esp)//23.我想说同上 movl $.LC0, (%esp) //24.我想说同上 call printf//25.我想说同上 leave ret .size print2, .-print2.globl main .type main, @functionmain: leal 4(%esp), %ecx andl $-16, %esp pushl -4(%ecx) pushl %ebp movl %esp, %ebp pushl %ecx subl $20, %esp movl $3, 8(%esp)//1.和上面那个程序一样的压栈操作 movl $2, 4(%esp)//2.我想说同上 movl $1, (%esp)//3.我想说同上 call print2//4.我想说同上 movl $0, (%esp) call exit .size main, .-main
>更多相关文章
- 09-29如何通过wrap malloc定位C/C++程序的内存泄漏
- 02-25打车软件大战升级,补贴还能维持多久?
- 12-23BMP文件右旋90度[c语言]
- 12-23寻找直方图中面积最大的矩形(C语言版)
- 12-23[ndk,2]ndk开发案例和错误处理
- 12-23[ndk,1]ndk开发,C语言入门讲解
- 12-23C语言连续存储实现队列机制
- 12-23Objective-c 数据类型
首页推荐
佛山市东联科技有限公司一直秉承“一切以用户价值为依归
- 01-11全球最受赞誉公司揭晓:苹果连续九年第一
- 12-09罗伯特·莫里斯:让黑客真正变黑
- 12-09谁闯入了中国网络?揭秘美国绝密黑客小组TA
- 12-09警示:iOS6 惊现“闪退”BUG
- 12-05亚马逊推出新一代基础模型 任意模态生成大模
- 12-05OpenAI拓展欧洲业务 将在苏黎世设立办公室
- 12-05微软质疑美国联邦贸易委员会泄露信息 督促其
- 12-05联交所取消宝宝树上市地位 宝宝树:不会对公
- 12-04企业微信致歉:文档打开异常已完成修复
相关文章
24小时热门资讯
24小时回复排行
热门推荐
最新资讯
操作系统
黑客防御