• C/C++函数调用过程的汇编代码分析(arm64指令集)


    函数调用过程的汇编代码分析(arm64指令集),顺便关注一下栈空间的分配与回收。

    本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
    叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
    1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
    2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
    3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

    下述C语言程序包含了一个简单的add()函数,该函数接受两个整型参数,计算并返回两者的和。

    main()函数调用执行了add()函数来计算3+2的和,并将计算结果打印出来。

    #include 
    
    int add(int a, int b){
        int t =  a + b;
        return t;
    }
    
    int main(){
        int x = 3;
        x = add(x,2);
        printf("%d",x);
        return 1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    为了从机器指令层面理解上述函数调用过程中的传参、跳转、返回等行为,我们在浏览器中访问了下述网站:
    在这里插入图片描述
    在粘贴相关代码并选择ARM64 gcc 9.3选项后,我们得到下述汇编语言指令序列:

    add(int, int):
            sub     sp, sp, #32
            str     w0, [sp, 12]
            str     w1, [sp, 8]
            ldr     w1, [sp, 12]
            ldr     w0, [sp, 8]
            add     w0, w1, w0
            str     w0, [sp, 28]
            ldr     w0, [sp, 28]
            add     sp, sp, 32
            ret
    .LC0:
            .string "%d"
    main:
            stp     x29, x30, [sp, -32]!
            mov     x29, sp
            mov     w0, 3
            str     w0, [sp, 28]
            mov     w1, 2
            ldr     w0, [sp, 28]
            bl      add(int, int)
            str     w0, [sp, 28]
            ldr     w1, [sp, 28]
            adrp    x0, .LC0
            add     x0, x0, :lo12:.LC0
            bl      printf
            mov     w0, 1
            ldp     x29, x30, [sp], 32
            ret
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    第2 ~ 11行:函数add()的实体

    第2行:将栈指针(stack pointer)寄存器sp的值减去32,结果仍存储到sp中。显然,该指令事实上为自动变量分配了栈空间。

    第10行:在函数返回前,将栈指针(stack pointer)寄存器的值增加32,结果仍存储到sp中。显然,该指令事实上回收了栈空间,回收的数量与分配的数量一致。

    第3行:将寄存器w0中的32位数值存储到内存地址sp+12处,结合后续代码可以知道,sp+12处存储的即是形参a。

    第4行:将寄存器w1中的32位数值存储到内存地址sp+8处,结合后续代码可以知道,sp+8处存储的即是形参b。

    第5行:将存储于sp+12处的形参a值装入寄存器w1。

    第6行:将存储于sp+8处的形参b值装入寄存器w0。

    第7行:将寄存器w1和w0的值相加,结果存入w0,此处事实上相加的即是形参a和b。

    第8行:将寄存器w0中的32位数值存入地址sp+28处。可以推测,sp+28处存储的即为自动变量t。

    第9行:将sp+28处的自动变量t装入寄存器w0, 结合后续代码可知,w0寄存器被用于向函数的调用者返回计算结果。

    第11行:返回x30寄存器所标识的地址继续执行,此时,x30寄存器所标识的地址为上述代码的第22行。

    第14 ~ 29行:函数main()的实体

    第17行:将整数3存入w0寄存器。

    第18行:将w0中的32位值存入sp+28处。合理推测,sp+28处为变量x的存放位置。

    第19行:将整数2存入w1寄存器,结合add()代码可知,add()通过w1寄存器获得参数b。

    第20行:将sp+28处的变量x的值存入寄存器w0。结合add()代码可知,add()通过w0寄存器获得参数a。

    第 21行:在准备好函数调用的参数(a,b分别在w0和w1寄存器内)后,branch & link指令完成两件工作:

    • 将pc+4的值存入x30寄存器,其中,pc表示当前指令地址(第21行),pc+4即指向当前指令的下一条,也就是第22行。
    • 跳转到add()函数函数,即第2行。

    当add()函数完成任务后,第11行ret指令将返回x30寄存器标识的地址继续执行,即返回代码的第22行。

    第22行:将w0寄存器的23位数值装入内存地址sp+28处。结合add()函数代码可知,此时的w0寄存器事实上存储的是add()函数的计算结果。这行代码事实上取得了add()函数的返回值,并将其存入x变量(sp+28)。

    在代码的后续部分,我们还看到main()函数对printf()函数的调用… 略。

    总结

    • 函数执行过程中,确实通过修改sp(栈顶指针)的值来分配和回收栈空间,栈空间用于存储自动变量。
    • 在寄存器够用的情况下,程序会尽量通过寄存器传参,并获取返回值。因为寄存器的访问速度比内存快得多。

    为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

    简洁的C及C++
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
    Python编程基础及应用
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造

    如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

    Python编程基础及应用

    Python编程基础及应用实验教程
    在这里插入图片描述

  • 相关阅读:
    线性代数-矩阵的逆
    Simulink| “双碳”背景下汽车减少碳排放建模与仿真
    谈谈你对mvc和mvvm的理解
    Docker 常用命令
    ssm及springboot整合shiro
    DCMM的定义和基础条件
    Win11应用商店一直转圈解决办法
    02设计模式——单例模式
    视频播放 (三) 视频列表
    Redis 实现限流策略
  • 原文地址:https://blog.csdn.net/SeaBiscuitUncle/article/details/127627019