浅聊C程序的编译、链接
浅聊C程序的编译、链接
我们编写一个C/C++程序后一般怎么在操作系统运行的,想要变成可执行文件,一般需要经过编译与链接。
其中编译细分为:预处理(
Preprocesssing
)、编译(Compilation
)、汇编(Assembly
)。链接(
Linking
)是一个“打包”的过程,它将所有的目标文件以及系统组件组合成一个可执行文件
下面一个基础的main.cpp为例使用gcc工具,详细介绍其中每一步过程。
测试程序
#include <stdio.h>
int main(void){
printf("hello world!\n");
return 0;
}
编译
预编译
预编译是在源代码编译之前做一些文本性质的操作。包括删除注释、执行预处理指令。
- 宏定义指令:将所有的#define删除,并且展开所有的宏定义
- 条件编译指令:处理所有的条件预编译指令,比如#if #ifdef #elif #else #endif等
- 头文件包含指令:处理#include 预编译指令,将被包含的文件插入到该预编译指令的位置
- 特殊符号指令:预编译器可研识别一些特殊的符号,例如:删除所有注释 “//”和”/* */”
- 添加行号和文件标识,以便编译时产生调试用的行号及编译错误警告行号
- 保留所有的#pragma编译器指令,因为编译器需要使用它们
gcc -E 表示预处理(Preprocess)指定的源文件,但不进行编译(Compile),-o输出生成 *.i
文本文件
gcc -E main.c -o main.i
编译
这一步时编译是把预处理的文件,编译成汇编语言代码。
gcc -S 编译把预处理的c文件变成汇编代码
gcc -S main.i -o main.s
汇编
这一步是把main.s汇编代码,汇编翻译为计算机能够识别的01二进制的机器码,生成的叫目标文件。在linux中就是ELF格式的文件,文件类型为Relocatable File 可重定位文件,只是该文件此时还没有进行链接,不能直接运行。
gcc -c 指令就是编译汇编语言代码成机器码,但是不进行链接。
gcc -c main.s -o main.o
也可以执行as汇编编译器。
as main.s -o main.o
这里使用file main.o可以输出的是ELF 64-bit LSB relocatable文件。nm main.o查看目标文件的符号。
链接
上一步我们已经得到了一个可重定位文件main.o,怎么把main.o变成可执行文件呢,下面就需要链接器来进行链接。
这里额外提下linux ELF文件的四种格式,第一种就是上面提到的Relocatable File 可重定位文件,第二种就是下面的执行文件Executable File 可执行文件,另外两种就是我们常见的Shared Objected File 共享目标文件(动态库跟静态库),跟特殊的Core Dump File 核心转储文件。
链接过程就是是把程序的目标文件与所需的所有附加的目标文件链接(动态库跟静态库)起来,其中主要的步骤分为地址与空间的分配、符号决议、重定位。最终确定程序中函数调用、变量引用的地址,生成可执行文件。这里使用gcc命令把main.o输出成可执行文件main,通过ldd查看链接的系统库linux-vdso,libc.so,ld-linux-x86-64.so等动态库。
/usr/lib/x86_64-linux-gnu/crt1.o
、/usr/lib/x86_64-linux-gnu/crti.o
、/usr/lib/x86_64-linux-gnu/crtn.o
:这些是启动文件,分别对应程序的入口、初始化代码和终止代码。-lc
代表链接libc.so,-dynamic-linker /lib64/ld-linux-x86-64.so.2
指定动态链接器
ld -o main /usr/lib/x86_64-linux-gnu/crt1.o /usr/lib/x86_64-linux-gnu/crti.o main.o -lc /usr/lib/x86_64-linux-gnu/crtn.o
总结
我们上面通过一个最基础的hello world的c程序实验了一个.c文件一步步成为一个可执行的ELF格式文件,初步解释了程序编译与链接的基础知识。其中linux常见的nm,objdump,readelf,ldd,ld
命令没有做具体说明,本博客在linux的常见命令中在做具体的分析。实际可执行文件如何被linux系统装载运行有时一个很复杂的过程,后续CPP博文再做分析。