蘭雅sRGB 个人笔记 https://262235.xyz
提供编程和电脑应用视频教程,工具和源代码
C, C++, Python Programming, Source Code, Video

旧Hexo博客 | Github | IP定位WebAPI | Docker Hub
编程中文文档 | 网盘分享 | 中文Linux命令

牛码学编程: 最简单C语言代码和汇编解释

VS Code 编写一个最简单 C 语言程序代码: a.c

int main(){
  int c = 0;
  c++;
  return c;
}

使用 VC2022 编译器命令行编译,运行,显示计算结果

cl   .\a.c
.\a.exe
$LASTEXITCODE
  • cl 是VC编译器的编译程序, PowerShell 里使用 $LASTEXITCODE 用来显示之前运行程序的返回值

VC2022 编译使用 dumpbin.exe 工具可以用来显示程序的汇编代码

dumpbin.exe /DISASM .\a.obj

Dump of file .\a.obj
main:
  0000000000000000: 48 83 EC 18        sub         rsp,18h
  0000000000000004: C7 04 24 00 00 00  mov         dword ptr [rsp],0
                    00
  000000000000000B: 8B 04 24           mov         eax,dword ptr [rsp]
  000000000000000E: FF C0              inc         eax
  0000000000000010: 89 04 24           mov         dword ptr [rsp],eax
  0000000000000013: 8B 04 24           mov         eax,dword ptr [rsp]
  0000000000000016: 48 83 C4 18        add         rsp,18h
  000000000000001A: C3                 ret

  • 下面是对你提供的汇编指令的逐行解释:

汇编代码解析

main:
  0000000000000000: 48 83 EC 18        sub         rsp,18h
  • sub rsp, 18h: 从栈指针 (rsp) 中减去 0x18 (24十进制)。这为函数的局部变量和返回地址留出空间。
  0000000000000004: C7 04 24 00 00 00  mov         dword ptr [rsp],0
                    00
  • mov dword ptr [rsp], 0: 将 0 移动到栈顶(即刚刚调整的 rsp 指向的位置),初始化局部变量为 0
  000000000000000B: 8B 04 24           mov         eax, dword ptr [rsp]
  • mov eax, dword ptr [rsp]: 将栈顶的值(即之前存储的 0)加载到寄存器 eax 中。
  000000000000000E: FF C0              inc         eax
  • inc eax: 将 eax 中的值加 1,所以此时 eax 的值变为 1
  0000000000000010: 89 04 24           mov         dword ptr [rsp], eax
  • mov dword ptr [rsp], eax: 将 eax 中的值(即 1)存回栈顶。
  0000000000000013: 8B 04 24           mov         eax, dword ptr [rsp]
  • mov eax, dword ptr [rsp]: 再次将栈顶的值(此时为 1)加载到 eax 中。
  0000000000000016: 48 83 C4 18        add         rsp, 18h
  • add rsp, 18h: 释放之前为局部变量分配的栈空间,恢复 rsp 的值。
  000000000000001A: C3                 ret
  • ret: 返回到调用该函数的地方。此时,eax 中的值(1)通常作为函数的返回值。

总结

这段汇编代码实现了一个简单的函数,它:

  1. 在栈上分配空间。
  2. 初始化一个局部变量为 0
  3. 将该变量的值加 1
  4. 最后返回这个值(1)。

这个过程展现了如何操作栈以及基本的寄存器使用。


使用 Risc-V linux 来学习 C语言代码,转成 RV 汇编代码

  • 把C语言代码保存成 001.c

    int main(){
    int c = 0;
    c++;
    return c;
    }

    我们可以使用 gcc 命令把源码转成汇编代码来学习

    gcc -S 001.c 
    gcc -c -march=rv64g 001.c
    objdump -d 001.o

这三行命令的逐行解释:

1. gcc -S 001.c

  • 命令: gcc -S 001.c
  • 解释:

    • gcc 是 GNU C 编译器。
    • -S 选项告诉编译器生成汇编代码,而不是生成目标文件或可执行文件。
    • 001.c 是输入的 C 源文件。
  • 结果: 该命令会生成一个名为 001.s 的汇编源文件,包含了从 C 代码转换而来的汇编代码。

2. gcc -c -march=rv64g 001.c

  • 命令: gcc -c -march=rv64g 001.c
  • 解释:

    • -c 选项表示编译源文件为目标文件(即生成 .o 文件),而不进行链接。
    • -march=rv64g 指定生成代码的架构为 RISC-V 64 位通用架构(RV64G)。
    • 001.c 是输入的 C 源文件。
  • 结果: 该命令会生成一个名为 001.o 的目标文件,其中包含编译后的机器代码。

3. objdump -d 001.o

  • 命令: objdump -d 001.o
  • 解释:

    • objdump 是一个用来显示目标文件信息的工具。
    • -d 选项表示反汇编目标文件,显示机器代码对应的汇编指令。
    • 001.o 是输入的目标文件。
  • 结果: 该命令会输出 001.o 文件中机器代码的汇编表示,帮助开发者理解编译后的代码。

总结

这三行命令的流程是:

  1. 将 C 源代码转换为汇编代码(gcc -S)。
  2. 将 C 源代码编译为特定架构的目标文件(gcc -c)。
  3. 反汇编目标文件以查看机器代码的汇编指令(objdump -d)。

如图我们看到C语言转成后的 RISC-V 汇编代码

汇编代码解析

main:
.LFB0:
  • main:: 定义一个名为 main 的函数。
  • .LFB0:: 这是一个局部符号,通常用于调试信息。
    addi    sp, sp, -32
  • addi sp, sp, -32: 将栈指针 (sp) 向下移动 32 字节,为局部变量和返回地址分配空间。
    sd    s0, 24(sp)
  • sd s0, 24(sp): 将寄存器 s0 的值存储到栈中,位置为 sp + 24。这是保存调用者的 s0 的值,以便在返回时恢复。
    addi    s0, sp, 32
  • addi s0, sp, 32: 将 s0 的值设置为 sp + 32。这意味着 s0 现在指向一个局部变量的地址。
    sw    zero, -20(s0)
  • sw zero, -20(s0): 将 0 存储到 s0 - 20 的位置,即初始化一个局部变量为 0
    lw    a5, -20(s0)
  • lw a5, -20(s0): 从 s0 - 20 的位置加载值到 a5。此时 a5 中的值为 0
    addiw    a5, a5, 1
  • addiw a5, a5, 1: 将 a5 的值加 1,并存储回 a5。现在 a5 的值为 1
    sw    a5, -20(s0)
  • sw a5, -20(s0): 将 a5 中的值(1)存储回 s0 - 20 的位置。
    lw    a5, -20(s0)
  • lw a5, -20(s0): 再次从 s0 - 20 的位置加载值到 a5,此时 a5 的值为 1
    mv    a0, a5
  • mv a0, a5: 将 a5 的值移动到 a0。在 RISC-V 中,a0 通常用于函数的返回值。因此,a0 现在也为 1
    ld    s0, 24(sp)
  • ld s0, 24(sp): 从栈中恢复之前保存的 s0 的值。
    addi    sp, sp, 32
  • addi sp, sp, 32: 恢复栈指针 (sp),将其向上移动 32 字节,释放之前分配的栈空间。
    jr    ra
  • jr ra: 跳转到返回地址,结束函数的执行并返回到调用该函数的位置。

总结

这段 RISC-V 汇编代码实现了一个简单的函数,它:

  1. 在栈上分配空间。
  2. 保存寄存器 s0 的值。
  3. 初始化一个局部变量为 0
  4. 将该变量的值加 1
  5. 将结果(1)作为返回值返回。

这个过程展示了 RISC-V 中栈的使用、寄存器的操作以及函数返回值的传递。

本原创文章自由转载,转载请注明本博来源及网址 | 当前页面:兰雅sRGB个人笔记 » 牛码学编程: 最简单C语言代码和汇编解释