跳转至内容
  • 版块
  • 最新
  • 标签
  • 热门
  • Online Tools
  • 用户
  • 群组
折叠
品牌标识

D2Learn Forums

  1. 主页
  2. Blogs | 博客
  3. pinkie_ctfer's Blog
  4. C 0x00(C语言是如何进行函数调用的)

C 0x00(C语言是如何进行函数调用的)

已定时 已固定 已锁定 已移动 pinkie_ctfer's Blog
c++
4 帖子 3 发布者 153 浏览
  • 从旧到新
  • 从新到旧
  • 最多赞同
登录后回复
此主题已被删除。只有拥有主题管理权限的用户可以查看。
  • 妈耶厥了妈 离线
    妈耶厥了妈 离线
    妈耶厥了
    写于 最后由 妈耶厥了 编辑
    #1

    C语言是如何进行函数调用的,换句话说C语言进行函数调用时都发生了些什么

    先写一段简单的C语言程序(不传参)

    #include <iostream>
    int test(){
        int a=0;
        return 0;
    }
    int main()
    {
        test();
    }
    

    这里我们简单的调用了一个函数test,我们对其进行反汇编,查看汇编代码,我用的编译器是gcc,使用clang会有不同的结果但都是大同小异

    test():
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], 0
            mov     eax, 0
            pop     rbp
            ret
    main:
            push    rbp
            mov     rbp, rsp
            call    test()
            mov     eax, 0
            pop     rbp
            ret
    

    首先我们知道,rbp是栈的基指针,rsp是栈的顶部的指针。人话说rsp、rbp这两个指针夹着栈。
    在调用的时候执行了一条指令

    push rbp
    

    也就是把rbp入栈,就是先要把rbp的值暂时存起来,以后要用到。接着是

    mov rbp, rsp
    

    将rsp赋值给rbp,也就是将rbp指针移动至rsp。这样做是为了给test函数开辟一个属于他自己的栈。
    我会搞一个动画、可能会直观一些

    首先说明32位、64位下的函数传参有一些不同

    传参的情况下(64位)

    int test(int b,int c,int d,int e,int f, int g,int h,int i){
        b=1;
        c=2;
        d=3;
        e=4;
        f=5;
        g=6;
        h=7;
        i=8;
    }
    int main()
    {
        int b=0;   
        test(1,2,3,4,5,6,7,8);
    }
    

    汇编如下

    test:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], edi
            mov     DWORD PTR -8[rbp], esi
            mov     DWORD PTR -12[rbp], edx
            mov     DWORD PTR -16[rbp], ecx
            mov     DWORD PTR -20[rbp], r8d
            mov     DWORD PTR -24[rbp], r9d
            mov     DWORD PTR -4[rbp], 1
            mov     DWORD PTR -8[rbp], 2
            mov     DWORD PTR -12[rbp], 3
            mov     DWORD PTR -16[rbp], 4
            mov     DWORD PTR -20[rbp], 5
            mov     DWORD PTR -24[rbp], 6
            mov     DWORD PTR 16[rbp], 7
            mov     DWORD PTR 24[rbp], 8
            nop
            pop     rbp
            ret
    main:
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16
            mov     DWORD PTR -4[rbp], 0
            push    8
            push    7
            mov     r9d, 6
            mov     r8d, 5
            mov     ecx, 4
            mov     edx, 3
            mov     esi, 2
            mov     edi, 1
            call    test
            add     rsp, 16
            mov     eax, 0
            leave
            ret
    

    可以看到传参的顺序是从右到左的,至于为什么要这样传参是因为栈的性质,先进后出第一个参数最后入栈,那么取出的时候是第一个取出的,这样设计的话就可以支持可变参数了。
    然后在看这一段

            mov     r9d, 6
            mov     r8d, 5
            mov     ecx, 4
            mov     edx, 3
            mov     esi, 2
            mov     edi, 1
    

    这是主调函数(就是调用其他函数的那个函数)把参数放到了寄存器里,是这样的,在64位C程序中函数调用优先使用寄存器(快)64位CPU的寄存器很多的可以看到顺序

    • edi(rdi)
    • esi(rsi)
    • edx(rdx)
    • ecx(rcx)
    • r8d
    • r9d
      如果参数更多的话,使用栈传参。
      但是即使他用了寄存器传参他也一定会放到栈里的,这个操作是在test函数里实现的
    test:
            push    rbp
            mov     rbp, rsp
            mov     DWORD PTR -4[rbp], edi
            mov     DWORD PTR -8[rbp], esi
            mov     DWORD PTR -12[rbp], edx
            mov     DWORD PTR -16[rbp], ecx
            mov     DWORD PTR -20[rbp], r8d
            mov     DWORD PTR -24[rbp], r9d
    

    这一段。到这里你一定会感到疑惑🤔,为什么

            mov     DWORD PTR 16[rbp], 7
            mov     DWORD PTR 24[rbp], 8
    

    这两行和其他两行不一样的说
    都是int 前面的占4个字节,这两个占8个字节,并且前面的是减,后面的是加。
    然后还是从16开始的,好奇怪!

    思考

    • rbp寄存器的大小.
    • pc寄存器大小.
    • 再想想为什么函数调用后能回到调用它的地方.

    如果想通了这两件事,问题就迎刃而解了,如果没想明白,下一篇文章将会解答
    至于为什么下面是占据8个字节全因为push指令,他一次会压入8个字节(64位cpu),所以就要配合人家
    然后为什么上面是4个字节,因为我这个编译器下int占据4个字节。有可能其他编译器64位程序中int 占据8个字节

    传参的情况下(32位)

    未完待续。。。

    sunrisepeakS 1 条回复 最后回复
    2
    • sunrisepeakS 离线
      sunrisepeakS 离线
      sunrisepeak d2learn-dev
      在 回复了 妈耶厥了 最后由 编辑
      #2

      @妈耶厥了 可以再加一个参数, 显示函数传参是怎么完成的

      妈耶厥了妈 1 条回复 最后回复
      0
      • 妈耶厥了妈 离线
        妈耶厥了妈 离线
        妈耶厥了
        在 回复了 sunrisepeak 最后由 编辑
        #3

        @sunrisepeak 有道理,完善了

        1 条回复 最后回复
        1
        • kxb520K 离线
          kxb520K 离线
          kxb520
          写于 最后由 编辑
          #4

          太

          强

          了

          👍

          1 条回复 最后回复
          0

          • 登录

          • 没有帐号? 注册

          • 登录或注册以进行搜索。
          d2learn forums Powered by NodeBB
          • 第一个帖子
            最后一个帖子
          0
          • 版块
          • 最新
          • 标签
          • 热门
          • Online Tools
          • 用户
          • 群组