逐行解析 - 从汇编看 C 语言
前言
本章我们将通过几个示例,本文以 Linux 系统为例,深入 C 语言的汇编代码,这能帮助我们更好理解程序的工作原理,例如栈的工作机制。
本文需要 C 语言基本语法、GCC 编译、GDB 调试的前置芝士。
如何调试 C 语言汇编代码
不需要安装额外的工具,直接使用 GDB 进行汇编代码调试即可,当然通过配置你的 IDE 也可以进行更舒适的调试。
首先书写源文件,假设我们已经书写了 main.c
文件,然后按照下面的方法编译:
1 | gcc main.c -o main.s -S # 生成汇编代码 |
我们只要不在源代码中添加调试信息,而只在汇编代码中添加调试信息,即可直接使用 GDB 调试汇编代码。
1 | gdb main |
一段简单的汇编代码
在这段代码中,我们不对代码如何工作作介绍,仅用于介绍 C 语言汇编代码的基本框架。在之后的代码中,我们将不再解释框架性质的代码。
源码解释
1 |
|
1 | .file "main.c" # 文件名 main.c |
递归 - 栈是如何工作的?
我们希望通过这个章节,介绍栈是如何工作的。这里我们将在最后详细模拟函数调用中的栈变化。
源码解释
1 |
|
1 | .file "main.c" |
栈空间变化
1 | ============================================================ |
malloc - 指针是如何工作的?
我们将以三层 malloc 为例,了解指针不断索引的过程。我们将给出工作流程图,然后希望通过汇编优化它。
源码解释
1 |
|
1 | .file "main.c" |
工作流程
sequenceDiagram participant code as code <br> 代码常量 participant edi as rdi/edi <br> 第一参数寄存器 participant esi as rsi/esi <br> 第二参数寄存器 participant rax as rax/eax <br> 第一通用寄存器 participant *(rbp-24) as *(rbp-24) <br> 栈空间地址 participant rbx as rbx <br> 通用寄存器 participant rdx as rdx <br> 通用寄存器 critical alloc int *** code->>edi: (int)edi = sizeof(int **) edi->>rax: (int ***)rax = (int ***)malloc(edi) rax->>*(rbp-24): (int ***)*(rbp-24) = (int ***)rax note over *(rbp-24): int *** end critical alloc int ** and connect code->>edi: edi = sizeof(int *) edi->>rax: (int **)rax = (int **)malloc(edi) rax->>rdx: (int **)rdx = (int **)rax note over rdx: int ** critical int *** points to int ** *(rbp-24)->>rax: (int ***)rax = (int ***)*(rbp-24) rdx->>rax: *(int ***)rax = (int **)rdx end end critical alloc int * and connect *(rbp-24)-->>rax: (int ***)rax = (int ***)*(rbp-24) rax-->>rbx: (int **)rbx = *(int ***)rax note over rbx: int ** code->>edi: (int)edi = sizeof(int) edi->>rax: (int *)rax = (int *)malloc(edi) note over rax: int * critical int ** points to int * rax->>rbx: *(int **)rbx = (int *)rax end end critical indexing and assignment *(rbp-24)->>rax: (int ***)rax = (int ***)*(rbp - 24) rax->>rax: (int **)rax = *(int ***)rax rax->>rax: (int *)rax = *(int **)rax critical int * prints to int code->>rax: *(int *)rax = 10 end end critical indexing and printf *(rbp-24)->>rax: (int ***)rax = (int ***)*(rbp - 24) rax->>rax: (int **)rax = *(int ***)rax rax->>rax: (int *)rax = *(int **)rax rax->>esi: (int)esi = *(int *)eax critical prepare for printf code->>rax: (char *)rax = "%d\n" rax->>edi: (char *)rdi =(char *)rax code->>rax: (int)eax = 0 note over edi, rax: (void) = printf(rdi, esi) end end code->>rax: (int)eax = 0 note over rax: return value
尝试优化
显而易见,编译器生成的汇编代码做了许多 “愚蠢” 的事情:
alloc int ** and connect
流程中使用rdx
寄存器,而在alloc int * and connect
流程中使用rbx
寄存器,其中虚线部分通过一种 “愚蠢” 方式实际从rdx
向rbx
拷贝。这里我们可以少用一个寄存器,并减少若干次拷贝。indexing and assignment
流程重新从int ***
索引到int *
,实际上一流程结束rax
寄存器存储的已是int *
,编译器遗忘了rax
中的当前值。这里我们可以直接复用rax
,减少若干次索引。- 同样地,
indexing and printf
流程中,我们可以复用rax
,减少若干次索引。
1 | .file "main.c" |
sequenceDiagram participant code as code <br> 代码常量 participant edi as rdi/edi <br> 第一参数寄存器 participant esi as rsi/esi <br> 第二参数寄存器 participant rax as rax/eax <br> 第一通用寄存器 participant *(rbp-24) as *(rbp-24) <br> 栈空间地址 participant rbx as rbx <br> 通用寄存器 critical alloc int *** code->>edi: (int)edi = sizeof(int **) edi->>rax: (int ***)rax = (int ***)malloc(edi) rax->>*(rbp-24): (int ***)*(rbp-24) = (int ***)rax note over *(rbp-24): int *** end critical alloc int ** and connect code->>edi: edi = sizeof(int *) edi->>rax: (int **)rax = (int **)malloc(edi) rax->>rbx: (int **)rbx = (int **)rax note over rbx: int ** critical int *** points to int ** *(rbp-24)->>rax: (int ***)rax = (int ***)*(rbp-24) rbx->>rax: *(int ***)rax = (int **)rdx end note over rax, rbx: 少用一个寄存器, 减少若干次拷贝 end critical alloc int * and connect code->>edi: (int)edi = sizeof(int) edi->>rax: (int *)rax = (int *)malloc(edi) note over rax: int * critical int ** points to int * rax->>rbx: *(int **)rbx = (int *)rax end end critical int * prints to int (no indexing) note over rax: 减少若干次索引 code->>rax: *(int *)rax = 10 end critical prepare for printf (no indexing) note over rax: 减少若干次索引 rax->>esi: (int)esi = *(int *)eax code->>rax: (char *)rax = "%d\n" rax->>edi: (char *)rdi =(char *)rax code->>rax: (int)eax = 0 note over edi, rax: (void) = printf(rdi, esi) end code->>rax: (int)eax = 0 note over rax: return value
循环 - 循环是如何工作的?
这里我们通过一个线性筛的示例讲解循环的工作流程。
源码解释
1 | .file "test.c" |
评论