GCC 相关汇总

◇ 编译过程

一个 C/C++ 文件要经过预处理 (preprocessing)、编译 (compilation)、汇编 (assembly) 和链接 (linking)
等 4 步才能变成可执行文件:

  • 预处理 (Preprocessing): 在编译过程的开始阶段,预处理器会处理源代码。这包括宏替换、头文件包含、条件编译等操作。

  • 编译 (Compilation): 编译器将预处理后的源代码转换为汇编语言或直接生成目标机器代码。

  • 汇编 (Assembly): 汇编器将汇编语言代码转换为机器代码。在一些高级语言的编译过程中,这一步骤可能被省略。

  • 链接 (Linking): 链接器将多个目标文件及其依赖关系合并成一个可执行文件。这包括解决符号引用、地址重定向等操作。

◇ 常用命令

▷ 动态库相关

制作和使用:

1
2
3
4
5
6
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c

# 可以使用多个.o 文件生成动态库
gcc -shared -o libsub.so sub.o sub2.o sub3.o
gcc -o test main.o -lsub -L /path/to/libsub.so/

运行:
先把 libusb.so 放到 PC 或板子上的 /lib 目录,然后就可以运行 test 程序。

或者放在某个自定义目录 /a,然后执行:

1
2
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a
./test

如果想要当前用户永久生效,可以修改 ~/.bashrc 文件,末尾添加:

1
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/a

然后运行以下命令,使其在当前终端中立即生效:

1
source ~/.bashrc

▷ 静态库相关

制作和使用:

1
2
3
4
5
6
7
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c

# 可以使用多个.o 文件生成静态库
ar crs libsub.a sub.o sub2.o sub3.o
# 如果库文件不在当前目录,需要指定它的绝对或相对路径
gcc -o test main.o libsub.a

运行:
程序中已包含,不需要把静态库 libsub.a 放到板子上。

▷ 其它命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 预处理,编译和汇编源文件,但是不作链接
gcc -c -o hello.o hello.c

# 查看预处理结果,比如头文件是哪个
gcc -E main.c
# 查看预定义的宏
gcc -E -dM
# 把所有的宏展开,存在 1.txt 里
gcc -E -dM main.c > 1.txt

# 列出头文件目录,库目录
echo 'main(){}' | gcc -E -v -

# 显示警告信息
gcc -Wall -c main.c

# 指定头文件目录
gcc -I xxx

# 打印 Makefile 格式依赖
$ gcc -M example.c
example.o: example.c header1.h header2.h
# 生成依赖文件 example.d
gcc -M -MF example.d example.c
# 既编译又生成依赖文件
gcc -c -o example.o example.c -MD -MF example.d
# 编译并生成依赖文件 abc.dep
gcc -Wp,-MD,abc.dep -c -o main.o main.c

# 查看编译详细信息
gcc -o hello hello.c -v

◇ Makefile

▷ 简单使用

- 目标相关

1
2
3
4
5
6
7
8
9
10
test: a.o b.o c.o
gcc -o test $^

%.o: %.c
gcc -c -o $@ $<

clean:
rm *.o test

.PHONY: clean

执行 make,即生成第一个目标 test

执行 make clean,执行 clean 目标。

.PHONY 是一个特殊的目标,用于指定一些伪目标 (phony targets)。伪目标通常是不生成对应文件的目标,而是执行一些特定的操作。

使用 .PHONY 可以避免与实际文件名相冲突的问题,如果有一个叫 clean 的文件存在,make 可能会认为 clean 文件是最新的,从而跳过执行清理操作。通过声明 .PHONY,告诉 make 不要考虑文件名,而总是执行 clean 目标下的命令。

- 赋值相关

1
2
3
4
5
6
7
8
9
10
11
A := $(C)
B = $(C)
C = abc
D ?= xyz

all:
@echo A = $(A)
@echo B = $(B)
@echo D = $(D)

c += 123

make D = 321 的结果:

1
2
3
A =
B = abc 123
D = 321

一些说明:

1
2
3
4
:=  # 即时变量
= # 延时变量
?= # 延时变量,第一次定义才起效,命令中定义的优先级 > 文件中第一次定义
+= # 在变量后面附加内容

- 函数相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
A = a b c
# 名称加后缀
B = $(foreach f, $(A), $(f).o)

C = a b c d/
# 筛选
D = $(filter %/, $(C))
E = $(filter-out %/, %(C))

# 返回当前目录符合条件的文件名
F = $(wildcard *.c)

F2 = a.c b.c c.c d.c e.c abc
# 返回真实存在的文件名
F3 = $(wildcard $(F2))

# 返回变量值并对符合条件的值进行替换
dep_F = $(patsubst %.c, %.d, $(F2))

all:
@echo B = $(B)
@echo D = $(D)
@echo E = $(E)
@echo F = $(F)
@echo F3 = $(F3)
@echo dep_F = $(dep_F)

结果:

1
2
3
4
5
6
7
8
9
10
$ ls
a.c b.c c.c Malefile

$ make
B = a.o b.o c.o
D = d/
E = a b c
F = a.c b.c c.c
F3 = a.c b.c c.c
dep_F = a.d b.d c.d d.d e.d abc

- 依赖相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
objs = a.o b.o c.o

dep_files := $(patsubst %, .%.d, $(objs))
dep_files := $(wildcard $(dep_files))

# 所有警告当作错误,添加头文件路径
CFLAGS = -Werror -Iinclude

test: $(objs)
gcc -o $@ $^

ifneq($(dep_files),)
include $(dep_files)
endif

%.o: %.c
gcc -c -o $@ $< -MD -MF .$@.d

clean:
rm *.o test

distclean:
rm $(dep_files)

.PHONY: clean distclean

▷ 一个模板

地址:链接

◇ 交叉编译

以 arm 为例:

1
2
3
4
5
# 在 /usr/local/ 下建立交叉编译器的安装目录 arm
sudo mkdir /usr/local/arm

# 将下载的交叉编译器包解压到 /usr/local/arm 目录下
sudo tar -jxvf cross-4.2.2-eabi.tar.bz2 -C /usr/local/arm/

j 告诉 tar 使用 bzip2 解压缩。在 tar 中,j 通常表示使用 bzip2 压缩。

解压成功后,修改 PATH 环境变量:

1
2
3
4
sudo vim /etc/profile

# 用户级别
vim ~/.bashrc

在文件为加入交叉编译器 arm-linux- 所在的路径:

1
2
3
4
5
6
export PATH=$PATH:/usr/local/arm/4.2.2-eabi/usr/bin

# 另一个例子
# export ARCH=arm
# export CROSS_COMPILE=arm-linux-gnueabihf-
# export PATH=$PATH:/path/to/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linuxgnueabihf/bin

更新一下配置文件 /etc/profile

1
2
3
4
source /etc/profile

# 用户级别
source ~/.bashrc

测试:

1
2
3
4
arm-linux-gcc -v

# 另一个例子
# arm-linux-gnueabihf-gcc -v

或者写一个测试程序 helloworld,交叉编译下,看是否能在开发板上运行:

1
2
3
4
5
6
7
8
9
# 交叉编译测试程序
arm-linux-gcc helloworld.c -o helloworld

# 把生成的可执行文件 helloworld 复制到 NFS 的挂载目录下
sudo cp ./helloworld /nfsboot

# 开发板执行
./helloworld
# 开发板使用 NFS 挂载 rootfs,nfsboot 是 NFS 的挂载目录

◇ 交叉调试工具 GDB

◇ 参考内容

  1. 【嵌入式Linux学习】6、交叉编译环境的搭建,单文件编译Hello Linux!. https://bbs.huaweicloud.com/blogs/333600
  2. 交叉编译和交叉调试环境搭建及使用. https://developer.aliyun.com/article/244367