课程简介

课程地址:【结合汇编3小时彻底理解C语言本质】 https://www.bilibili.com/video/BV1NhM4zxEm7?vd_source=b235e9c478ba07e2678b1ac01bb439c6

结合汇编3小时彻底理解C语言本质,讲解C语言函数,语句,结构体,指针,数组,浮点数的汇编实现

C语言结构

gcc -E class01.c -o class01.i将源码预处理得到源程序(中间文件)


如图所示,实际上就是暴力展开 + 字符串替换

gcc -S class01.i -o class01.s将中间文件编译得到:

X86汇编结构:

利用 gcc -C class01.s -o class01.o 编译代码,此时代码已经不可阅读。
利用 objdump -S class01.o 查看代码

这样我们就可以看到每条汇编指令对应的机器指令

可以通过 readelf -a class01.o 查看每个段在程序中的偏移
当然,我是在windows环境下,所以使用 objdump 替代

1
2
objdump -x class01.o  # 查看头信息
objdump -d class01.o # 反汇编


x86汇编结构

变量定义以及其汇编细节

1. .data 段(已初始化的全局变量、静态变量)

  • 存储:已初始化的非 const 全局变量或静态变量。

  • 示例:

    1
    2
    int a = 5;         // 全局,存储在 .data
    static int b = 3; // 静态,存储在 .data

2. .rodata 段(只读数据段)

  • 存储:所有 const 修饰的全局变量和字符串字面量。

  • 示例:

    1
    2
    const int c = 10; // 存储在 .rodata
    char *s = "hello"; // 字符串字面量存储在 .rodata

3. .bss 段(未初始化或初始化为 0 的全局变量)

  • 存储:未初始化或初始化为 0 的全局变量、静态变量。

  • 示例:

    1
    2
    3
    int d;            // 存储在 .bss
    static int e; // 存储在 .bss
    int f = 0; // 存储在 .bss

4. static 修饰的变量

  • 特性:文件私有,作用域仅限当前源文件,链接属性为 local,无法通过 extern 访问。

  • 存储:

    • 初始化:存储在 .data.bss

    • const static:存储在 .rodata

  • 段名:不会有通用的 .local_var 段,正确描述是链接属性为 Local,段依然是 .data.bss.rodata

5. 无修饰的全局变量

  • 链接属性:全局可见,默认 external linkage,可被 extern 引用。

  • 存储:

    • 已初始化:.data

    • 未初始化:.bss

    • const.rodata

总结

变量类型 存储段 作用域 是否可被 extern 访问
全局变量(已初始化) .data 全工程 可以
全局变量(未初始化/0 初始化) .bss 全工程 可以
const 全局变量 .rodata 全工程 可以
static 全局变量 .data/.bss 当前文件(文件私有) 不可以
const static 变量 .rodata 当前文件(文件私有) 不可以

一般来说,已初始化的全局变量和静态变量分配在 .data 段;
const 修饰的不可变全局变量和字符串字面量分配在 .rodata 段(只读数据段);
未初始化的全局变量或初始化为 0 的变量分配在 .bss 段,由操作系统在运行时自动清零。
static 修饰的变量具有文件私有属性,它们不会出现在全局符号表中,即使使用 extern 关键字也无法访问,存储位置仍可能在 .data.bss.rodata 段,但链接属性为 local(本地符号),而不会存在所谓的 .local_var 段。
未加修饰的全局变量具有全局可见性,可以被其他 C 文件通过 extern 关键字访问。

变量命名规则

C语言函数的汇编实现


一般来说,函数中的普通局部变量存储在栈帧空间中,它们的生命周期随函数调用而动态变化;而定义的静态局部变量存储在全局段空间(如 .data.bss.rodata 段),其生命周期贯穿整个程序运行过程,但作用域仅限于定义它的函数内部。
静态的函数和静态的变量一样,都具有私有保护,无法被导出使用。

C语言语句的汇编实现

1. 赋值(Assignment)

对应汇编:数据传送指令

  • 核心指令:mov

  • 含义:将数据从一个寄存器/内存位置传送到另一个寄存器/内存位置

  • 示例:

    1
    2
    mov eax, 5      ; 将 5 赋值给 eax
    mov ebx, eax ; 将 eax 的值赋给 ebx
  • 总结表述:
    赋值语句在汇编中通常对应数据传送指令,如 mov

2. 函数调用(Function Call)

对应汇编:调用指令

  • 核心指令:call

  • 含义:跳转到子程序,同时压栈当前指令地址(返回地址)

  • 示例:

    1
    call func       ; 调用 func 函数
  • 总结表述:
    函数调用在汇编中对应 call 指令。

3. 函数返回(Return)

对应汇编:栈清理与返回指令

  • 核心指令:leave + ret

  • 含义:

    • leave:恢复栈帧(等价于 mov esp, ebp + pop ebp

    • ret:从当前函数返回,跳转到压栈的返回地址

  • 示例:

    1
    2
    leave
    ret
  • 总结表述:
    函数返回在汇编中通常由 leaveret 实现。

4. 分支与循环(Branch & Loop)

对应汇编:比较与跳转指令

  • 核心指令:

    • 比较:cmp

    • 跳转:je(等于跳转)、jne(不等跳转)、jgjljmp(无条件跳转)

  • 示例:

    1
    2
    3
    cmp eax, ebx    ; 比较 eax 与 ebx
    je label ; 如果相等,跳转到 label
    jmp end ; 无条件跳转到 end
  • 分支结构(如 if-else)和循环结构(如 while、for)都是依赖 cmpjump 的组合。

  • 总结表述:
    分支和循环控制在汇编中通常通过 cmp(比较指令)与各种条件跳转(如 jejnejmp)组合实现。

全面总结

C 语言中的基本语句与汇编指令的对应关系大致如下:

  • 赋值操作: 对应 mov 等数据传送指令。

  • 函数调用: 对应 call 指令。

  • 函数返回: 通常由 leaveret 指令完成。

  • 分支与循环: 依赖 cmp 比较指令和条件跳转(如 jejnejmp)组合实现。

C运算符优先级

printf()详解

define预处理

整型实现

整型详解

整数加减乘除的汇编实现


虽然 有 add 和 sub 之分,但是内部都是基于 加法器实现的。


类型转换

浮点数实现

浮点数舍入原则:就近舍入

浮点数运算

但是这样会出现大数吃小数的现象,即小数在对阶的时候丢失精度了!

浮点数转换规则

浮点数运算指令 - fpu

指针 数组 字符串