本文主要对 C 程序的编译,链接等过程做一个简单的介绍,环境是 Ubuntu18 + gcc,代码如下:
main.h
头文件代码:
int add(int a,int b)
main.c
文件代码:
#include "main.h"
int main()
{
int age_1 = 12;
int age_2 = 13;
int age_all = add(age_1,age_2);
return age_all;
}
int add(int a,int b){
return a+b;
}
比较便捷的编译方法是直接执行以下编译命令:
gcc -o main main.c
上面得 -o
是指定输出文件名。但是为了演示整个编译的过程。下面一步一步执行。
第一步,预处理,就是把 .c 文件的 include 宏指令,全部包含进来。命令如下:
gcc -E -o main.i main.c
上面的 -E
选项是告诉 gcc 不用编译或者链接程序。
可以打开 main.i
文件查看,main.h
头文件已经被包含进来了。
第二步,编译,也就是把 C程序 翻译成 汇编程序,命令如下:
gcc main.i -S -o main.s
gcc 的命令参数解析程序比较智能,无论我把 main.i
放在开头还是末尾,他都能识别到这是输入文件。跟 FFmpeg 的命令行解析类似,FFmpeg 命令行用 -i
指定输入,如果一个参数不是输入文件,就是输出文件参数。
生成的 main.s 就是汇编代码,如下:
.file "main.c"
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl $12, -12(%rbp)
movl $13, -8(%rbp)
movl -8(%rbp), %edx
movl -12(%rbp), %eax
movl %edx, %esi
movl %eax, %edi
call add
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.globl add
.type add, @function
add:
.LFB1:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %edx
movl -8(%rbp), %eax
addl %edx, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE1:
.size add, .-add
.ident "GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
.section .note.GNU-stack,"",@progbits
这里要注意, 里面 调了 call add, 所以EIP寄存器的指令会进行跳转,如下:
到这里,虽然 main.s 已经是汇编指令,汇编指令是机器指令的可读形式,但是现在的 main.s 还是 ASII码,CPU 是不可能能执行 ASII码的,必须转成二进制,也就是机器码。再执行以下编译命令:
gcc -c -o main.o main.s
上面的 -c 选项是 Compile and assemble, but do not link,此时 main.o 里面就是机器码指令。用notepad++ 查看 main.o 的二进制内容,如下图:
基本上是一条汇编对应一条机器码指令,既然是对应的,就找一下 相关的机器码,看一下相邻的汇编指令的机器码是不是也是相邻的。可以用以下命令查看:
objdump -sd main.o
如下图:
在 nodepad++ 搜索 8b 55 f8 8b 45 f4
,这是两条 mov 指令的机器码。
可以看到整个机器码是连在一起的。
这里讲一个扩展知识点,上面 gcc 把 C程序翻译成 汇编指令,都是从 ASII码转成 ASII码,文本转文本。汇编器也是 gcc,把 汇编指令 转成 机器指令,就是 ASII码转二进制,也就是文本转二进制。还有一种编译技术是二进制翻译,也就是二进制转二进制,也就是翻译器把 某条机器指令 转换成另一条机器指令,通常在虚拟化领域会用到二进制翻译技术。
现在还剩 最后一步,生成可执行文件,命令如下:
gcc -o main main.o
上面的命令会将 main.o 与C标准输入输出库进行连接,最终生成程序 main 可执行文件。
由于笔者的水平有限, 加之编写的同时还要参与开发工作,文中难免会出现一些错误或者不准确的地方,恳请读者批评指正。如果读者有任何宝贵意见,可以加我微信 Loken1,QQ:2338195090。