关于C语言中函数调用和参数传递机制的探讨(二 .传递一个参数)

时间:2021-11-29 12:28:19
2.函数原型: int function(int i)

    现在有了参数了,也有了返回值了,相对来说更比较复杂了。这里就得引入%esp寄存器值的变化了,不然就难以把问题分析清楚了,如果想形象一点地描述那就画图,自己画个图根据我的数据变化一起分析吧。看看一段简单的C代码:

    // C Code

    int function(int i)
    {
        return 2 * i;
    }

    int main(void)
    {
        int j = function(10);
        return 0;
    }

    之所以些这么简单只是为了我们分析问题的方便,懂得个原理就算是复杂的其实稍微再分析一下也就懂了。我们从main开始分析吧:

    main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $24, %esp
        andl    $-16, %esp
        movl    $0, %eax
        addl    $15, %eax
        addl    $15, %eax
        shrl    $4, %eax
        sall    $4, %eax
        subl    %eax, %esp        #到这里其实和前面的例子基本一样,就不分析了
        movl    $10, (%esp)
        call    function
        movl    %eax, -4(%ebp)
        movl    $0, %eax
         leave
        ret

    看看上面的汇编代码,和前面一样的不分析。但是其中有句不一样:subl    $24, %esp ; 因为主函数里有两个临时变量i, j;这里为了有足够的空间留给临时变量所以干脆在堆栈里腾出24个字节空间。在看看下面的代码:

    movl    $10, (%esp) #====> %esp = 800, (800) = 10
,其中800是我们假设的地址值,(800)表示地址800的内容这里的(%esp)指的是%esp地址里的内容,
刚才我们假设这时候%esp的值是800, 那么地址为800的内容就是10了。执行函数调用了,注意在调用函数前其实是先把函数调用指令
call之后的地址压栈,也就是call之后那条指令的IP值压栈,所以这时候 %esp =
796;这里要弄明白为什么要把下条指令地址压栈,假设如果不把IP值压栈,那么当函数调用完毕后怎么能找到函数调用时的地址呢?也就是说如果没把IP压
栈,那么函数调用完之后就回不到原来的执行地址了,就会造成程序执行顺序的错误!

下面列出函数function的汇编代码:

    function:
        pushl   %ebp
        movl    %esp, %ebp
        movl    8(%ebp), %eax
        addl    %eax, %eax
        popl    %ebp
        ret
    
    pushl %ebp; 经过这条指令后 %esp值减4,所以这时候%esp值是792。下面这句:

    movl    %esp, %ebp  #==============> %ebp = 792, %esp = 792, (792) = %ebp ;其中(792)表示地址792的内容

     movl    8(%ebp), %eax  #========> %eax = 10

    上面这句很多人可能不明白了,8(%ebp)指的是什么?8(%ebp)等于 : (%ebp + 8) ,这里注意,%ebp + 8
是表示一个地址值,加上括号表示存储在该地址上的内容。
所以8(%ebp)其实就是地址为800的值,看前面地址800的值刚好是10!所以这句其实是把10复制给%eax寄存器.

    addl    %eax, %eax    #======> %eax = 20

    相当于2 * %eax, %eax这时候等于20了,刚好是实现了C代码中的 (2 * i);

    popl    %ebp        #=========> 恢复%ebp寄存器的值, %esp这时候等于796

    ret                    #=========>  函数调用完毕返回,这句其实是把刚才压栈的IP值弹出栈,执行这条指令后 %esp = 800

                        # 800!想想我们在调用函数的时候%esp也是800啊!这就是实现了“清栈”了,就是把调用函数所在的栈清除了!

    好了,函数 function的汇编代码分析完了,现在回头继续看看main函数里的下一条指令了。接下来是这句:

    movl    %eax, -4(%ebp)

    %eax寄存器存放的是什么?看function函数的代码,可以知道其实就是(2 *
i)的值,所以返回值其实是通过%eax来传递的!传递到-4(%ebp)里去了,-4(%ebp) = (%ebp - 4);
-4(%ebp)到底是什么呢?看看C代码,返回值传给变量j,那么-4(%ebp)会不会就是j呢?答案是肯定的!我们先看看%ebp的值是什么。看看
main函数的汇编代码,可以得出,%ebp其实指向了main函数的栈底部,但记不记得前面说的subl    $24,
%esp是为临时变量而留出的空间?没错,-4(%ebp) 就是存储在临时变量区域!也就是变量 j 了。