C语言中主函数调用另外一个函数,汇编代码理解

栈的理解

由于主函数在执行过程中,需要从主函数跳到被调用的函数,这就涉及到保存当前函数状态,进入被调函数的操作。这个保存状态的操作就需要用到栈。
栈的结构如下:
在这里插入图片描述
关于栈的理解有几点要注意的:

  • 栈是由高地址向低地址方向增长
  • rsp是堆栈寄存器,里面存放的是栈顶指针的地址
  • 栈顶指针只有在需要扩大栈大小或者缩小栈大小的时候,才会向低地址移动,否则向入栈和出栈是根据栈顶指针的偏移量来操作的,栈顶指针不需要移动。

示例代码

主函数call_proc.c

#include"proc.h"
long call_proc()
{
        long  x1 = 1; int  x2 = 2;
        short x3 = 3; char x4 = 4;
        proc(x1, &x1, x2, &x2, x3, &x3, x4, &x4);
        return (x1+x2)*(x3-x4);
}

被调函数proc.c

void proc(long  a1, long  *a1p,
          int   a2, int   *a2p,
          short a3, short *a3p,
          char  a4, char  *a4p)
{
        *a1p += a1;
        *a2p += a2;
        *a3p += a3;
        *a4p += a4;
}

汇编代码

利用执行gcc -Og -S xxx.c分别生成两者的汇编代码call_proc.s和proc.s如下(只列出关键部分):
call_proc.s

.file   "call_proc.c"
        .text
        .globl  call_proc
        .type   call_proc, @function 
call_proc: 
.LFB0:
        .cfi_startproc
        endbr64
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        subq    $32, %rsp
        .cfi_def_cfa_offset 48
        movl    $40, %ebx
        movq    %fs:(%rbx), %rax
        movq    %rax, 24(%rsp)
        xorl    %eax, %eax
        movq    $1, 16(%rsp)
        movl    $2, 12(%rsp)
        movw    $3, 10(%rsp) 
        movb    $4, 9(%rsp)
        leaq    12(%rsp), %rcx
        leaq    16(%rsp), %rsi
        leaq    9(%rsp), %rax
        pushq   %rax  
        .cfi_def_cfa_offset 56
        pushq   $4    
        .cfi_def_cfa_offset 64
        leaq    26(%rsp), %r9
        movl    $3, %r8d
        movl    $2, %edx
        movl    $1, %edi
        call    proc@PLT
        movslq  28(%rsp), %rcx
        addq    32(%rsp), %rcx
        movswl  26(%rsp), %edx
        movsbl  25(%rsp), %eax
        subl    %eax, %edx
        movslq  %edx, %rax
        imulq   %rcx, %rax
        addq    $16, %rsp
        .cfi_def_cfa_offset 48
        movq    24(%rsp), %rdi
        xorq    %fs:(%rbx), %rdi
        jne     .L4
        addq    $32, %rsp
        .cfi_remember_state
        .cfi_def_cfa_offset 16
        popq    %rbx
        .cfi_def_cfa_offset 8
        ret

proc.s

       .file   "proc.c"
        .text
        .globl  proc
        .type   proc, @function
proc:
.LFB0:
        .cfi_startproc
        endbr64
        movq    16(%rsp), %rax
        addq    %rdi, (%rsi)
        addl    %edx, (%rcx)
        addw    %r8w, (%r9)
        movl    8(%rsp), %edx
        addb    %dl, (%rax)
        ret
        .cfi_endproc

汇编代码分析

先分析call_proc.s中的汇编代码

更改栈的大小

subq $32, %rsp //将rsp减少32个字节,因为栈的增长方向是大地址往小地址,相当于增大栈的容量

保存局部变量

//将局部变量压栈,rsp前面的数字是相当于栈顶指针的偏移量
movq    $1, 16(%rsp) 
movl    $2, 12(%rsp)
movw    $3, 10(%rsp)
movb    $4, 9(%rsp)

做完上述操作,栈的示意图如下:
在这里插入图片描述

保存被调函数的形参

因为该被调函数有8个形参,但是最多只有6个形参能保存到寄存器中,因此有两个被保存到了栈中,且是形参的最后两个值。

 //将形参保存到寄存器中
 leaq    12(%rsp), %rcx  //load effective adress,用于一个内存地址直接赋给目的操作数
 leaq    16(%rsp), %rsi
 
 //将形参保存到栈中,
 leaq    9(%rsp), %rax
 pushq   %rax  //将寄存器中的数压栈
 pushq   $4  // 将立即数压栈
 
 //将形参保存到寄存器中
 leaq    26(%rsp), %r9
 movl    $3, %r8d
 movl    $2, %edx
 movl    $1, %edi

参数和形参的对应关系如下图所示:
在这里插入图片描述

调用被调函数

call    proc@PLT

调用被调函数前,会把调用代码的的下一条执行指令压栈,方便从被调函数中返回到主函数时候,还能往下执行。
在这里插入图片描述

返回

... //一系列逻辑操作
ret

被调函数汇编代码

逻辑运算

movq    16(%rsp), %rax

// 直接从寄存器中取出运算数
addq    %rdi, (%rsi)
addl    %edx, (%rcx)
addw    %r8w, (%r9)

//从栈中取出运算数
movl    8(%rsp), %edx
addb    %dl, (%rax)

返回

ret

总结

  • 嵌套的函数调用,需要用栈来保存局部变量、形参、返回时候的指令地址
  • C语言的参数是按照从右到左的顺序压到栈中的

参考

C语言函数调用的底层实现
汇编语言中mov和lea的区别有哪些?

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值