C语言编译流程讲解
1、 预处理
1)预处理命令
在 C 语言的编译过程中,预处理是第一个阶段。它的主要任务是处理源代码文件中的预处理指令,将其转换为编译器可以识别的格式。
预处理主要包括以下几个方面:
- 宏替换(例如
#define
定义的宏) - 文件包含(例如
#include
头文件) - 条件编译(例如
#if
、#ifdef
等) - 注释移除
预处理的结果是一个新的源代码文件,这个文件通常比原始文件要大,因为预处理器会展开宏、插入头文件的全部内容等。这一结果会保存在一个临时文件中,并作为后续编译器输入。
示例:对两个源文件进行预处理
使用如下命令进行预处理:
jimmy@ubuntu:~/helloworld$ gcc -E hello.c -o hello.i
jimmy@ubuntu:~/helloworld$ gcc -E main.c -o main.i
命令参数说明:
-
-E
:Expand(展开)的缩写,表示仅执行预处理阶段。 -
.i
:Intermediate(中间)的缩写,通常预处理生成的文件以.i
作为后缀。
2、 编译
在 编译阶段,编译器会将预处理后的源代码文件(.i
文件)转换为 汇编代码(.s
文件)。
这个过程包括以下几个步骤:
- 词法分析
- 语法分析
- 语义分析
- 代码优化
编译器会检查源代码的语法和语义,生成对应的汇编代码。这是整个编译过程中最复杂、最耗时的阶段之一,决定了程序的正确性和性能。
编译命令示例:
jimmy@ubuntu:~/helloworld$ gcc -S hello.i -o hello.s
jimmy@ubuntu:~/helloworld$ gcc -S main.i -o main.s
参数说明:
-
-S
:**Source(源代码)**的缩写,表示只执行编译生成汇编代码。 -
.s
:**Assembly Source(汇编源码)**的缩写,表示输出为汇编代码文件。
1.7.4 汇编
1)汇编命令
在 汇编阶段,由 汇编器(Assembler) 将 .s
汇编代码翻译成目标机器的二进制代码(.o
文件)。这是机器可以直接执行的指令格式,虽然尚未链接成最终程序。
汇编器的主要任务包括:
-
符号解析
-
指令翻译
-
地址关联
-
重定位处理
-
简单的代码优化
汇编命令示例:
jimmy@ubuntu:~/helloworld$ gcc -c hello.s -o hello.o atguigu@ubuntu:~/helloworld$ gcc -c main.s -o main.o`
参数说明:
-
-c
:Compile or Assemble,表示将.s
或.c
文件编译为目标文件(机器码),但不进行链接。 -
-o
:Object 的缩写,表示输出的目标文件名,一般以.o
为后缀。
2)目标文件内容结构分析
汇编完成后生成的 .o
文件是目标文件,可以使用 objdump
工具查看其内容。
查看机器码和反汇编:
objdump -d main.o
3)常见的 section(段)
目标文件中被划分为多个段(section),不同段的功能不同:
-
.text
:包含可执行指令,是程序的主代码区。 -
.data
:包含已初始化的全局变量和静态变量,这些变量在程序启动时已经被赋初值。 -
.bss
:包含未初始化的全局变量和静态变量。不占用目标文件的实际空间,仅指示运行时要分配并清零的内存区域。 -
.rodata
:只读数据段,如字符串常量和其他只读常量。 -
.symtab
、.rel.text
、.debug_info
等其他段用于调试、符号表、重定位等目的。
1.7.5 链接
链接阶段由链接器完成,它将多个目标文件及所需库链接为最终的可执行文件。
链接器主要功能:
- 解析符号引用与定义(如函数、全局变量)
- 处理地址重定位
- 合并各目标文件和库文件
- 生成可执行程序或共享库文件
1)链接方式
(1)静态链接
将所有目标文件和库全部打包到一个可执行文件中,运行时不再依赖外部库。
gcc -static main.o hello.o -o main
# -static:强制使用静态库进行链接
(2)动态链接
程序运行时才加载库,减小体积,提升复用性。
方式一:默认动态链接
gcc main.o hello.o -o main # 不加 -static,默认使用动态链接
方式二:使用自定义动态库
生成动态库:
gcc -fPIC -shared -o libhello.so hello.o # -fPIC:生成位置无关代码 # -shared:生成共享库(.so)
链接生成可执行文件:
gcc main.o -L ./ -lhello -o main_d # -L ./:指定库路径 # -lhello:链接 libhello.so
-L ./表示额外的库文件位置为当前目录
运行时指定库路径:
LD_LIBRARY_PATH=./ ./main_d
(3)混合链接
部分库使用静态链接,其他使用动态链接。
生成静态库:
ar crv libhello.a hello.o
链接优先动态库(若存在):
gcc main.o -L ./ -lhello -o main
强制使用静态库(先删除 .so):
rm libhello.so
2)glibc 的库说明
-
动态库路径:
/usr/lib/x86_64-linux-gnu/libc.so
-
静态库路径:
/usr/lib/x86_64-linux-gnu/libc.a