1.GCC编译器的使用

- 预处理:预处理阶段会根据指令对源代码进行替换、宏展开等操作。在这个阶段,编译器会根据#include指令查找头文件,并将头文件的内容插入到main.c文件中。预处理后的文件通常以.i或.ii为扩展名。
- 编译:编译阶段将预处理后的文件翻译成汇编语言文件(通常使用. s扩展名)。编译器将C代码转换为汇编代码,将高级语言代码转换成底层机器语言的表示形式,同时进行语法检查和优化。
- 汇编:汇编阶段将汇编语言代码转换成机器码指令。汇编器将每条汇编指令转换为可执行的机器码,并生成目标文件(通常使用.o扩展名)。目标文件包含机器码指令和一些附加信息,如符号表等。
- 链接:链接阶段将目标文件与其他库文件进行组装,生成最终的可执行程序。在这个阶段,链接器会解析代码中的符号引用,并将其与库函数的定义进行关联。如果找不到某个符号的定义,链接将会失败,并报出”undefined reference“的错误。
1.1 常用编译选项
| 常用选项 | 描述 |
|---|---|
| -E | 预处理 |
| -S | 编译 |
| -c | 进行预处理、编译、汇编,但是不链接 |
| -o | 指定输出文件 |
| -I | 指定头文件目录 |
| -L | 指定链接时库文件目录 |
| -l | 指定链接哪一个库文件 |
| -v | 输出详细编译信息(比如说库文件查找路径) |
举例
1
2
3
4
5
6gcc -E -o hello.i hello.c // 预处理
gcc -S -o hello.s hello.i -v // 编译,输出编译信息
gcc -o hello hello.c // 输出名为 hello 的可执行程序,然后可以执行./hello
gcc -o hello hello.c -static // 静态链接
gcc -c -o hello.o hello.c // 先编译(不链接)
gcc -o hello hello.o // 再链接
1.2 编译多个文件
一起编译、链接:
1
gcc -o test main.c sub.c
分开编译,统一链接:
1
2
3gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
gcc -o test main.o sub.o
1.3 动态库和静态库
- 类 unix 系统,静态库为 .a(archive), 动态库为 .so(shared object)。
- windows 系统静态库为 .lib, 动态库为 .dll。
- 静态库被使用目标代码最终和可执行文件在一起,而动态库与它相反,它的目标代码在运行时或者加载时链接。
- 浅谈静态库和动态库
1.3.1 制作使用静态库
1 | gcc -c -o main.o main.c |
1.3.2 制作使用动态库
1 | gcc -c -o main.o main.c |
1.4 程序运行的基础知识
- 头文件:
<xx.h>- 在编译工具链指定include目录寻找
-I dir:指定头文件目录
- 链接:
-lxxx- 在编译工具链指定的lib目录寻找
-L dir:指定库文件所在目录
- 运行:寻找动态库
.so文件- 在运行程序的系统的指定/lib目录寻找
- 修改环境变量
LD_LIBRARY_PATH添加新路径,例如export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/new_lib
1.4.1 查找编译工具链的指定目录
1 | echo 'main(){}'| arm-buildroot-linux-gnueabihf-gcc -E -v - |
echo在标准输入中打印字符串”main(){}”,gcc命令最后的-表示从标准输入中获取输入数据,即gcc对于代码main(){}进行预处理,并输出详细编译信息。在这些编译信息中,会列出头文件目录(#include <…>)和库文件目录(LIBRARY_PATH)
1.4.2 运行时库的系统路径
在开发板上
/lib或者/usr/lib目录。或者在bash中添加新的库路径,见1.4
1.4.3 如何交叉编译开源软件
如果开源软件的目录中有configure文件,则使用交叉编译工具链arm-buildroot-linux-gnueabihf编译的万能命令如下:
1 | ./configure --host=arm-buildroot-linux-gnueabihf --prefix=$PWD/tmp |
- make执行makefile中编译命令,make install将生成的软件安装到系统对应目录中,例如
/usr/local/bin用于可执行文件,/usr/local/lib用于库文件等。 - 在上述命令中即为安装到tmp目录下的bin,lib,include目录中,然后在手动将对应文件放到工具链和板子上的对应目录中。
prefix=/usr/local:通常make会将生成的软件安装在这个目录中,为了不污染系统库,所以通过--prefix=$PWD/tmp指定安装目录
2. Makefile
- Makefile的核心规则:
1 | 目标 : 依赖1 依赖2 |
当目标文件不存在或者依赖文件比目标文件新的时候,就会执行命令。
一般来说,在Makefile中将编译代码文件分为预处理/编译/汇编和链接两步,当某个代码文件重新修改时,Makefile会根据修改时间仅对修改文件进行步骤一,然后重新链接,不必重新编译所有代码文件。
- make命令基本语法:
1 | make [目标] |
- 假设待编译的实例文件如下:
1 | //a.c |
2.2 Makefile编写
2.2.1 第一版
1 | test : a.o b.o c.o |
2.2.2 使用通配符
1 | test : a.o b.o c.o |
%.o : %.c:匹配模式规则%通配符用于匹配模式规则中的字符序列*通配符用于匹配文件名
$@:表示目标$<:表示第一个依赖文件$^:表示所有依赖文件.PHONY:声明clean为假想目标,防止目录下存在clean文件而导致命令不执行
2.2.3 变量
1 | A := xxx //即时变量,在定义时即确定 |
举例
1
2
3
4
5
6
7A := ${C}
B = ${C}
all:
@echo ${A}
@echo ${B}
C ?= abc
C += 123A是即时变量,在定义时C还未定义,所有A的值为空;B是延时变量,在使用时C已经定义,B的值为
abc 123
2.2.4 常用函数
1 | $(foreach var, list, text) # 遍历list的元素var,对于var生成text的格式 |
举例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18A = a b c
B = $(foreach f, $(A), $(f).o)
C = a b c d/
D = $(filter %/, $(C))
E = $(filter-out %/, $(C))
file1 = $(wildcard *.c) # 从当前目录下取出符合格式的文件名
file_name = a.c b.c d.c e.c abc
file2 = $(wildcard $(file_name)) # 判断file_name中的文件在当前目录下是否存在
dep_files = $(patsubst %.c, %.d, $(file_name))
all:
@echo B = $(B)
@echo D = $(D)
@echo E = $(E)
@echo file1 = $(file1)
@echo file2 = $(file2)
@echo dep_files = $(dep_files)输出:
1
2
3
4
5
6B = a.o b.o c.o
D = d/
E = a b c
file1 = a.c c.c b.c
file2 = a.c b.c
dep_files = a.d b.d d.d e.d abc
2.2.5 判断语句
1 | ifneq (condition1, condition2) |
2.2.6 考虑头文件
假设c.c文件包含了头文件c.h,并引用了其中的宏定义:
1 | //c.h |
由于Makefile仅在目标文件不存在或者依赖文件更新时才执行目录,所以如果在编译完成之后,再修改.h文件,则使用原来的Makefile不会更新所引用的宏,因此需要在Makefile中考虑头文件
使用gcc命令生成头文件依赖
1 | gcc -M c.c // 打印出头文件依赖 |
修改Makefile
1 | objs = a.o b.o c.o |
使用该Makefile,在首次编译之后,修改c.h文件,再一次执行Makefile:
1
2gcc -c -o c.o c.c -MD -MF .c.o.d
gcc -o test a.o b.o c.o包含头文件依赖,发现头文件c.h发生改变,所以重新编译c.c文件,最后重新链接生成可执行文件
CFLAGS = -Werror -Iinclude:为gcc增加编译选项-Werror:将警告视为错误-Iinclude:在当前目录的include目录下搜索头文件
2.3 通用Makefile模板
主目录下Makefile:
1 | # 指定编译工具链的前缀 |
主目录下Makefile.build:
1 | PHONY := __build |
子目录下Makefile示例:
1 | # 指定子目录中文件的编译选项 |