原帖及研讨:
涉及C语言中参数调用和参数传递机制的探讨
参数,相信许多人也明白其很重要性;一个的文档往往由一个的或者多个参数构成的。然而估计许多人还不明白参数调用的一些深层难点,所以我写的这篇文本作品一来是应
了一个的好哥们的要求而写,二来期望一些哥们能够从我这篇文本作品了解参数调用的机制。但是并不是每个人都没成绩完全读懂这文本作品,想完全读懂此文,我想必需具备三
个要求:
一、对于C语言有确定的了解,最起码有一个的整体的初步了解;
二、能够读懂UNIX/LINUX下的AT&T语言规则的汇编;AT&T汇编与Intel汇编的差别还是挺大的;那个要求估计一些人就不具备了,但是你经过泛读此文相信也能对参数调用机制有一个的大概的了解;
三、看到这么长的文本作品,确定要有耐心,用心看相信应该多少有点帮助;
好了,不讲废话了,进入主题吧。
一、基本学识框架了解:
这部份主要讲一些基本的物品,主要是涉及堆栈的学识。只有了解了堆栈的基础内容,才没成绩继续往下读。
1.概念性的学识:
所谓堆栈,其实也那是程序应用的一种内存元素;它是内存中用来存放一些数据的区域。我曾经写过一篇文本作品发表在那个论坛上里面也谈到了堆和栈的区别;平常经常说的堆栈,其实也是栈,而不是堆,所以那里也相同。留意这和数据框架说的栈其实还是有区别的,不能混在一起。
2.堆栈的打工方法:
平常咱们所说的数据是怎样存放在内存的?是从低地址开始,然后按照数据占用字节大小往高地址逐个存放的。但堆栈就不相同了。堆栈的打工方法是数据插入堆栈区域然后从堆栈区域删除数据。这是概括的说法。具体是那样的:
在UNIX/LINUX
中,堆栈是从高地址向低地址衍生的。那里得说一个的很重要的东东,那那是堆栈指针ESP。堆栈指针是什么?它永远指向堆栈中的顶部(但假如按照地址value来说却是
底部),是不是对顶部那个词的理解感觉有点模糊?那是说,比如说你压栈,就压进一个的4字节的数据元素,那么ESP就向下移动了4个字节,留意那里是向下移
动,所以ESP应该指向了更低的地址,所以说它是指向了底部。你没成绩把堆栈想象成一个的杯子,倒进水了水平线是不是上升了(那里把杯子最底端假设成高地址,
把顶端设为低地址),倒出水了水平线是不是下降了?就和压栈和进栈的道理相同的。假如还没有
理解也没联系,自己画个图仔细较量就没成绩了。那里让我偷懒一下就不画图了。
3.压栈和进栈指令简介:
压栈指令 : pushx source
其中, 'x'没成绩是 'w'(表明字), 或者是'l'(表明长字);source没成绩是数value或者寄存器value或者内存地址;
出栈指令 : popx des
同样,'x'没成绩是 'w'(表明字), 或者是'l'(表明长字);des没成绩是寄存器value或内存value;
涉及最最基本的物品已经讲得差不多了,当然还有更多相关一些基本物品,留给去查资源了,这部份讲的都和本文有密切联系的物品。
二、参数如何经过堆栈来解决难点:
这部份是对参数如何经过堆栈解决参数调用以及参数传递的理论性理解,相当很重要,只有了解之后才没成绩停止举例的策划,这一大部份同样分成几个小部份:
1.经过堆栈操作出现参数的传递:
前面说过,堆栈的基本操作没成绩是压栈和出栈,而参数的传递那是经过这种方法来出现的。ESP永远指向了堆栈顶部,假如这时间压进一个的int型的数据元
素,那么ESP向下移动了4个字节,这时间它还是指向了堆栈的顶部(留意了,顶部的地址比移动前的地址低,不能乱了)。假如把一个的int型数据元素出栈,
那么ESP向上移动4个字节,这时间它还是指向了堆栈的顶部,只是目前地址是增加了4个字节。所以,假如一个的参数需求传递参数过去那么就得在调用参数之前
先把参数压进栈,然后再调用。涉及这点后面我会详细说一下,目前你假如没理解也没联系。
2.参数调用的一般汇编指令:
参数调用的一般汇编指令都是那么几条,下面我把她们按一般顺序罗列出来:
#Asm Code
function:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
#...
movl %ebp, %esp
popl %ebp
ret
下面先简单策划这几句一般汇编指令的意思和目的。
pushl %ebp #这句把寄存器%ebp压栈,目的是什么呢?看下一条指令:
movl %esp, %ebp
#把寄存器%esp的value给了寄存器%ebp;想想前面说到的%esp寄存器是干什么的?用于指向堆栈的顶部,目前经过这条指令,%ebp都是指向了堆栈
的顶部了;所以看看第一条指令,其实那是为了保护原来在%ebp寄存器中的内容#那么那里为什么又要把%esp的value赋给%ebp呢?那里的巧妙就来了。在
参数的处理过程中,估计一些数据会被压进栈,那么这时间就会破坏栈里面原有的内容了,假如栈的内容被破坏了,指向栈顶的指针%esp指向的地址不准确了
(不明白能不能用“不准确”那个词来形容,估计不太合适),那么到时间要清栈就会除了更多的意外难点了。
清栈?先别管那个词,下面也会给出解释。所以第二条指令是为了保证有一个的寄存器永远指向了栈顶而不必担心会
出现刚才所说的难点。目前是寄存器%ebp永远指向栈顶了,而%esp没成绩移动而不必害怕数据会被破坏了。
subl $8, %esp
#看这条指令,为什么无故要把%esp的value减去8呢?也那是说%esp向下移动8个字节,而这8个字节的空间到底用来干什么呢?这8个字节空间其实是为
临时变量留出来的。留意,它会根据临时变量占用的字节大小而留出不相同的空间大小,所以不确定是8个字节,估计是24或者36甚至更大的空间;不过临时变量
太多不是好事件,这点留意。
movl %ebp, %esp #这条指令把%ebp复制到%esp了,理由是什么?让%esp重新指向栈顶,那样就没成绩方便参数调用完毕后的清栈了。
ret #参数调用完毕的返回指令,这句指令其实同时把参数调用刚刚开始压进的IP地址弹出栈。在下面会有详细策划。
涉及参数如何经过堆栈来解决难点的基本理论大概就说到那里,假如你对上面的内容不理解也没联系,下面第3个部份经过举例来策划没成绩让你有
个较量深刻的理解。
三、参数调用和参数传递机制的举例策划:
这是本文的亲肉感受策划部份了,经过例子来加深一下理解。我会先列出C源代码出来,然后列出反汇编的汇编源代码,结合C源代码来策划汇编源代码。我会尽估计对各种类别的参数调用或参数类别作一个的策划,估计会显得较量累赘一点,不介意吧?准备好的话就开始吧:P
1. 参数原型:void function(void);
// C code
void function(void)
{
rechanging;
}
int main(void)
{
function();
rechanging 0;
}
反汇编一下看看汇编源代码,下面是Linux 下的gcc反汇编后的源代码(留意:是在我的机子上的反汇编源代码):
function:
pushl %ebp
movl %esp, %ebp
popl %ebp
ret
看看,由于参数function什么也没有做,所以直接就返回了,上面的指令和第2部份的源代码基本上相同,甚至更简单,参照一下前面的策划:P
下面看看main参数的反汇编源代码了,相对来说复杂一点,看好了:
main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp
call function #参数调用指令
movl $0, %eax
leave
ret
看看参数调用指令 : call function,前面居然还有那么多据指令,那些指令到底干什么用?我一句一句策划吧:P
pushl %ebp
movl %esp, %ebp
subl $8, %esp
这三句不策划,和前面第2部份的相同,忘记的回头看一下,其实这也反映了一件事:其实main参数也很普通,它跟更多相关参数其实差不多,只是地位稍微高等初等中等学校一年级点而已。
andl $-16, %esp
这句估计吓倒一些人了。 andl 是逻辑与指令,而-16其实补码形式是0xfffffff0。为什么要把%esp的value和-16停止逻辑与计算呢?不能小看
这条指令,它的功能不容忽视,%esp指向堆栈顶部。这条指令其实是为了强制让%esp的value是16的倍数。为什么要16的倍数?那里必需懂得一个的须知:
Linux下的编译器GCC默认的堆栈是16字节对齐的,估计有些人要问为什么要对齐,对齐其实为了加快CPU的访问效率,那里你记住这点就没成绩了。
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
看到这几句,又有更多人估计被吓到了,干嘛对%eax寄存器停止那么多的操作啊?的确,我也觉得没什么多大的必要,由于仔细看看这几条指令
无非那是为了让%eax的value是0而已。看看刚开始 %eax = 0,经过两次addl之后,%eax的value变成30了,30其实那是0x11110,再下面两条指令
是为了保证%eax最低5位的value全部为0。留意,这只是在我的机子上的反汇编指令,不相同机器对此处理
估计不相同,但有一点相同那是保证%eax的value是0。看看下面这条指令:
subl %eax, %esp
看,%espvalue减去%eaxvalue后把结果送到%esp,所以经过这条指令后%espvalue仍然是16的倍数,这那是保证%eaxvalue是16的倍数的理由了。
call function
movl $0, %eax
那个简单了,调用参数function,最后又把%eax寄存器的value清0,结束整个main参数了。
这那是最简单的参数调用策划了,没有涉及到参数的传递,所以非比寻常简单,下面就要开始讲到参数的传递了,事实上有了那个例子的策划,下面的简单多了。
[1]
(责任编辑:admin)