高级语言和机器码

掌握从高级语言到机器代码的翻译过程,也需了解C语言中不同语句与汇编代码的对应关系,可能在大题中出现,能看懂就行。

编译器、汇编器、链接器

编译器 (Compiler)

编译器是一个软件程序,它将用高级程序设计语言(如C、C++、Java)编写的源代码转换为低级语言(通常是汇编语言)。编译器主要进行以下工作:

  1. 词法分析 (Lexical Analysis):将源代码分解成一系列的记号(tokens)。
  2. 语法分析 (Syntax Analysis):将记号组织成语法树,检查代码的语法结构。
  3. 语义分析 (Semantic Analysis):检查代码的语义正确性,比如类型检查。
  4. 优化 (Optimization):对代码进行优化,提高效率,减少资源消耗。
  5. 代码生成 (Code Generation):将优化后的代码转换为目标机器的汇编语言。

例如,GCC(GNU Compiler Collection)和MSVC(Microsoft Visual C++)都是流行的编译器。

汇编器 (Assembler)

汇编器是将汇编语言转换为机器语言的程序。因为汇编语言中的指令基本上是机器指令的直接映射(只是用符号代替了二进制代码),所以汇编器主要工作是:

  1. 解析指令:识别汇编代码中的指令和数据定义。
  2. 符号解析:解析标签和符号,将它们转换成地址或者数值。
  3. 生成机器代码:将汇编指令和操作数转换为对应的机器码。

链接器 (Linker)

链接器的作用是将由编译器生成的一个或多个目标代码文件(通常是汇编器生成的机器代码)合并为一个单一的可执行文件。在这个过程中,链接器:

  1. 解析外部引用:将不同代码模块之间相互引用的部分关联起来。
  2. 地址和存储分配:确定代码和数据在内存中的位置。
  3. 符号绑定:将程序中的符号(如函数和变量名)绑定到它们对应的内存地址。
  4. 重定位:调整代码和数据的引用,使它们指向正确的地址。
  5. 库链接:将程序使用的库中的代码合并到最终的可执行文件中。

链接器可以是静态链接器,它在程序开始执行之前完成所有的链接工作;也可以是动态链接器(运行时链接器),它在程序执行时进行链接。

选择结构语句

if (a > b) {
    max = a;
} else {
    max = b;
}
; 假设a, b的值分别存放在寄存器eax和ebx中
cmp eax, ebx         ; 比较a和b
jle else_label       ; 如果a <= b, 跳转到else_label
mov max, eax         ; a > b, max = a
jmp endif_label      ; 跳转到endif_label
else_label:
mov max, ebx         ; max = b
endif_label:

循环结构语句

while (count < 10) {
    count++;
}
; 假设count的值存放在寄存器ecx中
loop_start_label:
cmp ecx, 10          ; 比较count和10
jge loop_end_label   ; 如果count >= 10, 跳出循环
inc ecx              ; count增加
jmp loop_start_label ; 无条件跳回循环开始
loop_end_label:

过程调用

在汇编中调用函数包含如下步骤:

  • 参数传递:在函数调用之前,将参数值放入寄存器或栈中。
  • 调用指令:使用call指令将控制转移到函数的代码。

过程调用内部包含如下步骤:

  • 堆栈帧设置:在函数的开始处,设置局部变量所需的堆栈帧。
  • 执行函数体:执行函数中的代码。
  • 返回值处理:将函数的返回值放在一个特定的位置,通常是一个寄存器。
  • 堆栈帧恢复和返回:恢复调用者的堆栈帧并执行ret指令,这会跳转回调用点。

以下举一个实际的例子(了解即可):

int add(int x, int y) {
    return x + y;
}

void exampleFunction(int a, int b) {
    int sum = add(a, b);
    int localVariable = sum * 2;
    // ... 一些使用localVariable的代码 ...
}

// 调用exampleFunction
exampleFunction(10, 20)
; 假定 'a' 和 'b' 作为参数通过堆栈传递

.globl _add
_add:
    push ebp                ; 保存旧的基指针
    mov ebp, esp            ; 设置新的基指针
    mov eax, [ebp+8]        ; 将参数x移到eax,参数x在ebp+8的位置
    add eax, [ebp+12]       ; 将参数y加到eax中,参数y在ebp+12的位置
    pop ebp                 ; 恢复旧的基指针
    ret                     ; 返回,返回值在eax中

.globl _exampleFunction
_exampleFunction:
    push ebp                ; 保存旧的基指针
    mov ebp, esp            ; 设置新的基指针
    sub esp, 8              ; 在堆栈上分配8字节空间给局部变量

    ; 准备参数并调用 'add'
    push dword [ebp+12]     ; 将参数b压栈
    push dword [ebp+8]      ; 将参数a压栈
    call _add               ; 调用 'add'
    add esp, 8              ; 清理传递给 'add' 的参数

    ; 将返回值 'sum' 存储到局部变量
    mov [ebp-4], eax        ; 将eax的值('add' 的返回值)存储在 'sum' 的位置

    ; 创建另一个局部变量 'localVariable' 并计算 'sum * 2'
    mov eax, [ebp-4]        ; 将 'sum' 移到eax
    shl eax, 1              ; 将eax左移1位,相当于乘以2
    mov [ebp-8], eax        ; 将结果存储到 'localVariable'

    ; ... 更多使用 'localVariable' 的代码 ...

    ; 函数完成,清理堆栈,并恢复ebp
    mov esp, ebp            ; 重置堆栈指针
    pop ebp                 ; 恢复基指针
    ret                     ; 返回

; 函数调用的汇编表示
; 假设要传递给 exampleFunction 的参数为 10 和 20
; 先将参数推入堆栈,注意在32位架构中调用约定通常是从右到左传递参数

push 20         ; 将第二个参数 b 压入堆栈
push 10         ; 将第一个参数 a 压入堆栈
call _exampleFunction ; 调用 exampleFunction 函数
add esp, 8      ; 清理堆栈,移除参数(每个参数4字节,总共8字节)

; 在这里执行之后的代码
; ...

视频讲解