1- GCC编译

1.1 GCC编译四步骤:

  1. 预处理(Pre-Processing)

    gcc -E hello.c -o hello.i

展开宏,头文件,替换条件编译,删除注释,空行,空白

  1. 编译(compiling) ----------消耗资源最多

    gcc -S hello.i -o hello.s

    检查语法规范,将预处理后的文件转换为汇编语言代码

  2. 汇编(Assembling)

    gcc -C hello.s -o hello.o

    将汇编语言代码翻译成二进制形式的指令和数据,这些指令和数据可以直接在计算机上执行。

  3. 链接(Linking)

    gcc hello.o -o hello

GCC将目标文件与其他必要的库文件进行链接,生成最终的可执行文件。链接器会解析目标文件之 间的引用和符号,将它们连接在一起,生成一个完整的可执行程序。这个阶段还会处理一些额外 的任务,如符号重定位、库函数的解析等。

5ea26721c89c2d5d02cd0b63ff04c33a_gcc

1.2 GCC编译常用参数

GCC编译器提供了许多参数和选项,用于控制编译过程和生成的可执行程序的行为。下面是一些常用的GCC编译参数和选项:

  1. -c:只进行编译,生成目标文件,不进行链接操作。

  2. -o <file>:指定输出文件的名称。

  3. -g:生成调试信息,以便在调试程序时使用。

  4. -Wall:打开所有警告信息。

  5. -Werror:将警告视为错误,编译过程中出现警告将导致编译失败。

  6. -O<level>:指定优化级别,其中<level>可以是0、1、2、3或s。较高的级别会进行更多的优化,但编译时间可能会更长。嵌入式编程需要小心代码被优化掉

  7. -std=<standard>:指定所使用的C或C++标准,如-std=c11表示使用C11标准,-std=c++17表示使用C++17标准。

  8. -I<dir>:添加头文件搜索路径。

  9. -L<dir>:添加库文件搜索路径。

  10. -l<library>:链接指定的库文件。

  11. -D<macro>:定义一个宏。

  12. -U<macro>:取消定义一个宏。

  13. -E:只进行预处理操作,生成预处理后的文件。

  14. -S:只进行编译和汇编操作,生成汇编语言文件。

  15. -shared:生成共享库(动态链接库)。

  16. -static:生成静态链接可执行程序。

  17. -pedantic:启用更严格的标准检查。

  18. -pthread:链接线程库。

  19. -M:生成.c文件与头文件依赖关系用于Makefile,包括系统库的头文件。生成的依赖关系文件通常以.d为扩展名,可以使用-MF参数指定生成的文件名。

  20. -MM:生成.c文件与头文件依赖关系用于Makefile,包括系统库的头文件

# hello.c文件的头文件路径在该目录的inc文件夹内
gcc -I ./inc hello.c -o hello
# 生成依赖关系文件dependencies.d
gcc -M source.c -MF dependencies.d

一般来说,用-c生成二进制文件,囊括了-E-S步骤。

1.3 GCC编译出错

  1. collect2: error : ld returned 1 exit status

链接器出错。collect2为完成链接工作的链接器,ld调用链接器驱动链接器工作。

2- 静态库和动态库

在使用GCC编译器时,可以使用-static参数生成静态链接的可执行文件,使用-shared参数生成动态链接的共享库(动态库)。

2.1 静态库

  • 静态库是一组编译好的目标文件(通常是.o文件)的集合,它们被打包成一个单独的文件。

  • 在链接时,静态库的目标代码会被完整地复制到可执行文件中,使得可执行文件在运行时不再依赖于静态库。

  • 静态库的优点是简单易用,只需将库文件链接到可执行文件中即可,无需考虑库的加载和依赖关系。

  • 缺点是静态库会增加可执行文件的大小,而且如果多个可执行文件都使用同一个静态库,会造成代码的冗余。

2.2 动态库

  • 动态库是一组编译好的目标文件的集合,它们被打包成一个单独的文件,但在运行时并不会被完整地复制到可执行文件中。

  • 在链接时,可执行文件只包含对动态库的引用,而不是实际的目标代码。动态库会在运行时被动态加载到内存中,并与可执行文件共享。

  • 动态库的优点是可以被多个可执行文件共享,减少了重复代码的存储空间。同时,如果动态库发生更新或修复,只需替换动态库文件即可,无需重新编译可执行文件。

  • 缺点是动态库的使用稍微复杂一些,需要确保库文件在运行时能够被正确地加载和找到。

总结来说,静态库适用于简单的项目或者需要独立性较高的可执行文件,而动态库适用于多个可执行文件共享代码和灵活更新的情况。静态库加载快,动态库加载慢!

2.3 静态库制作

  1. 编写源代码:首先,编写你的库的源代码文件(通常是.c.cpp文件)。

  2. 编译源代码:使用GCC编译器将源代码编译为目标文件(.o文件)。例如,使用以下命令编译一个源文件 mylib.c

    gcc -c mylib.c -o mylib.o

    这将生成一个名为 mylib.o 的目标文件。

  3. 打包目标文件:使用ar命令将目标文件打包成静态库。例如,使用以下命令将目标文件 mylib1.omylib2.o打包成静态库 libmylib.a:(静态库命名规则需要以.a作为文件后缀,以lib开头)

    ar rcs libmylib.a mylib1.o mylib2.o

    这将生成一个名为 libmylib.a 的静态库文件。

    • r 选项用于向库中添加文件或更新现有文件。

    • c 选项用于创建库文件,如果库文件不存在则创建一个新的库文件。

    • s 选项用于创建索引表,提高链接速度。

  4. 创建静态库头文件:创建文件mylib.h,用来在里面声明函数,并在其他代码源文件中#include该头文件

    #ifndef MY_HEADER_FILE_H    // 防止头文件重复包含
    #define MY_HEADER_FILE_H
    ​
    /***** 头文件内容 *****/
    ​
    #endif

  5. 使用静态库:现在可以将生成的静态库文件 libmylib.a 与其他源文件一起编译,并将其链接到可执行文件中。例如,使用以下命令编译 main.c 并链接静态库 libmylib.a

gcc main.c -o myprogram -L/path/to/library -lmylib -I /path/to/inc
  • -L 选项用于指定库文件的搜索路径。

  • -l 选项用于指定要链接的库文件,库文件名去掉前缀 lib 和后缀 .a

  • -I选项用于指定要链接的头文件

注意-L-l参数必须同时使用,不能因为库文件和程序文件在同一目录就不加-L

或者直接将main.c和库文件一起编译:(库文件须放在程序文件之后)

gcc main.c libmylib.a -o myprogram

这样就可以使用静态库 libmylib.a 提供的函数和功能来构建可执行文件 myprogram

动态库制作

动态库制作需要生成与位置无关的代码(使用-fPIC参数)

  1. 编写源代码:首先,编写你的库的源代码文件(通常是.c.cpp文件)

  2. 编译源代码:使用GCC编译器将源代码编译为目标文件(.o文件)。例如,使用以下命令编译一个源文件 mylib.c

    gcc -c -fPIC source1.c -o source1.o
    gcc -c -fPIC source2.c -o source2.o
    ...
  3. 创建动态库:使用gcc将目标文件链接成动态库文件,可以使用以下命令:(动态库命名规则需要以.so作为文件后缀,以lib开头)

    gcc -shared source1.o source2.o ... -o libmylib.so
  4. 安装动态库(可选):将生成的动态库文件安装到系统的库目录中,以便其他程序可以使用。可以使用以下命令:

    sudo cp libmylib.so /usr/local/lib
    sudo ldconfig
  5. 使用动态库

    1)头文件包含

在使用动态库的源代码中,包含动态库提供的头文件,以便使用库的函数和类型。

2)编译源代码

gcc main.c -o program -L /path/to/library -llibrary -I /path/to/inc
  • -L 选项指定动态库的路径,

  • -l 选项指定要链接的动态库名称(去掉前缀 "lib" 和后缀 ".so")。

  • -I 选项用于指定要链接的头文件

tips:

使用动态库时,确保系统能够找到动态库文件,可以通过设置 LD_LIBRARY_PATH 环境变量来指定动态库的搜索路径,例如:

  1. 通过环境变量

export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH    # 可以保留原有的LD_LIBRARY_PATH值
export LD_LIBRARY_PATH=/path/to/library     # 直接替换覆盖原有的LD_LIBRARY_PATH值
  1. 永久生效

写入终端配置文件  然后 .bashrc 建议使用绝对路径
1) vim ~/.bashrc
2)  写入 export LD_LIBRARY_PATH = /path/to/library , 保存
3) . .bashrc  或者 source .bashrc  或者  重启终端   ---> 让修改后的.bashrc生效
  1. 将库文件拷贝到标准c库目录下,通常为 /lib下

  2. 配置文件法

1)  sudo vim /etc/ld.so.conf
2)  写入用户动态库路径,保存
3)  sudo ldconfig -v  使配置文件生效
4)  可以使用ldd program 查看program的包含的库

3- GDB调试工具

3.1 前提

在gcc编译的时候,添加-g 选项得到调试表,包含调试信息

3.2 开始调试

gdb a.out   # 开始调试a.out程序文件

3.3 调试参数

  • list 1 / l 1 : 列出源码。根据源码指定行号或者设置断点

  • break xxx / b xxx : 在xxx行位置设置断点

  • delete xxx / d xxx : 在xxx行删除断点

  • run / r : 运行程序

  • next / n : 下一条指令 ( 会越过函数 )

  • step / s : 下一条指令 ( 会进入函数 )

  • print xxx / p xxx : 查看xxx变量的值

  • continue : 继续执行断点后续指令

  • quit / q : 推出gdb当前调试

  • start : 单步执行,运行程序,停在第一行执行语句

  • backtrace / bt : 查看函数的调用的栈帧和层级关系, 列出当前程序正存活的栈帧

    • 栈帧:随着函数调用在stack上开辟的一片内存空间。用于存放函数调用时产生的局部变量和临时值。

  • info / i : 查看gdb内部局部变量的数值,如:info breakpoints

  • frame / f : 切换函数的栈帧

  • set : 设置变量的值 set var n=100

  • run argv[1] argv[2] : 调试时命令行传参

  • finish : 结束当前函数调用,返回到函数调用点

  • ptype xxx : 查看xxx的变量类型

  • display xxx : 设置观察变量xxx(用来一直跟踪某一个变量的值的变化

  • undisplay num : 取消观察编号为num的变量

  • enable breakpoints : 启用断点

  • disable breakpoints : 禁用断点

  • x : 查看内存 x /20xw 显示20个单元,16进制,4字节每单元

  • watch : 被设置的观察点的变量发生修改是,打印显示

  • i watch : 显示观察点

  • core文件 : ulimit -c 1024 开启core文件,调试时 gdb a.out core

4- Makefile项目管理

1个规则,2个函数,3个自动变量

4.1 用途

  • 项目代码编译管理

  • 节省编译项目时间

  • 一次编写终身受益

  • 操作示例文件:add.c sub.c mul.c dive.c main.c

4.2 命名

Makefile, makefile

4.3 规则

目标 : 依赖文件1 依赖文件2
    (一个tab缩进)  命令
​
### 例子:###
hello:hello.c hello.h
    gcc hello.c hello.h -o hello

ALL:指定makefile的终极目录

4.4 makefile工作原理

  • 若想生成目标, 检查规则中的所有的依赖文件是否都存在:

  • 如果有的依赖文件不存在, 则向下搜索规则, 看是否有生成该依赖文件的规则:

  • 如果有规则用来生成该依赖文件, 则执行规则中的命令生成依赖文件;

  • 如果没有规则用来生成该依赖文件, 则报错.

  • 如果所有依赖都存在, 检查规则中的目标是否需要更新, 必须先检查它的所有依赖,

  • 依赖中有任何一个被更新, 则目标必须更新.(检查的规则是哪个时间大哪个最新)

若目标的时间 > 依赖的时间, 不更新
若目标的时间 < 依赖的时间, 则更新

总结:

  • 分析各个目标和依赖之间的关系

  • 根据依赖关系自底向上执行命令

  • 根据依赖文件的时间和目标文件的时间确定是否需要更新

  • 如果目标不依赖任何条件, 则执行对应命令, 以示更新(如:伪目标)

联合编译时跳过未修改的文件(防止重复编译,节省资源)

main:main.o fun1.o fun2.o sum.o
    gcc -o main main.o fun1.o fun2.o sum.o
​
main.o:main.c
    gcc -c main.c -I./ -o main.o
fun1.o:fun1.c
    gcc -c fun1.c -o fun1.o
fun2.o:fun2.c
    gcc -c fun2.c -o fun2.o
sum.o:sum.c
    gcc -c sum.c -o sum.o

缺点: 冗余, 若.c文件数量很多, 编写起来比较麻烦。

4.5 变量

分为普通变量自带变量自动变量

4.5.1 普通变量

变量定义直接用 =

使用变量值用 $(变量名)

如:下面是变量的定义和使用
foo = abc           // 定义变量并赋值
bar = $(foo)        // 使用变量, $(变量名)

定义了两个变量: foo、bar, 其中bar的值是foo变量值的引用。

4.5.2 自带变量

除了使用用户自定义变量, makefile中也提供了一些变量(变量名大写)供用户直接使用, 我们可以直接对其进行赋值:

CC = gcc #arm-linux-gcc
CPPFLAGS : C预处理的选项 -I
CFLAGS:   C编译器的选项 -Wall -g -c
LDFLAGS :  链接器选项 -L  -l

4.5.3 自动变量

$@ 表示依赖列表中的文件名
$^ 表示依赖列表中的所有文件名, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项
$< 表示依赖列表中的第一个文件名,如果将该变量应用在模式规则中,可以将依赖列表中的依赖依次取出,套用模式规则
$? 所有在依赖列表里面比当前目标新的文件名,用空格隔开;
$+ 与$^相似,也是依赖列表中的文件名用空格隔开,不同的是这里包含了所有重复的文件名;
$* 显示目标文件的主干文件名,不包含后缀部分。

特别注意:自动变量只能在规则的命令位置中使用.

4.6 函数

4.6.1 wildcard函数

wildcard – 查找指定目录下的指定类型的文件
src=$(wildcard *.c)  //找到当前目录下所有后缀为.c的文件,将文件名组成列表赋值给src

4.6.2 patsubst函数

patsubst – 匹配替换
obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o

4.7 clean

清除编译生成的中间.o文件和最终目标文件

make clean 如果当前目录下有同名clean文件,则不执行clean对应的命令, 解决方案:

  • 伪目标声明:

.PHONY:clean

示例:

.PHONY:clean
clean:
    -rm -rf $(target) $(object) a.out       # "-"的作用是删除不存在的文件时不报错,顺序执行结束

使用make clean执行清理操作

4.8 模式规则

至少在规则的目标定义中要包含%, %表示一个或多个, 在依赖条件中同样可以使用 %, 依赖条件中的 %的取值取决于其目标: 比如: main.o:main.c fun1.o: fun1.c fun2.o:fun2.c, 说的简单点就是: xxx.o:xxx.c

例如:

target=main
object=main.o fun1.o fun2.o sum.o
$(target):$(object)
    $(CC) $^ -o $@
​
%.o:%.c
    gcc -c $< $(CPPFLAGS) -o $@

静态模式规则:

$(obj):%.o:%.c      # 在模式规则前面加上$(obj):意思是在obj中套用该模式规则
    gcc -c $< $(CPPFLAGS) -o $@

4.9 示例

src=$(wildcard  ./*.c)      # 等价于src=main.c fun1.c fun2.c sum.c,其中wildcard - 查找指定目录下的指定类型的文件
object=$(patsubst %.c,%.o,$(src))  # 等价于obj=main.o fun1.o fun2.o sum.o,其中,patsubst – 匹配替换
target=main
ALL:main
CC=gcc
CPPFLAGS=-I./
​
$(target):$(object)
    $(CC) $^ -o $@
​
%.o:%.c
    $(CC) -c $< $(CPPFLAGS) -o $@
​
.PHONY:clean ALL
clean:
    -rm -f $(target) $(object)