本文最后更新于 190 天前,其中的信息可能已经过时,如有错误请发送邮件到 echobydq@gmail.com
关于 Linux 系统编程的前导课程学习笔记,共涉及 GCC
GDB
Makefile
三个部分

GCC(英文全拼:GNU Compiler Collection)是 GNU 工具链的主要组成部分,是一套以 GPL 和 LGPL 许可证发布的程序语言编译器自由软件,由 Richard Stallman 于 1985 年开始开发。
GCC 原名为 GNU C 语言编译器,因为它原本只能处理 C 语言,但如今的 GCC 不仅可以编译 C、C++ 和 Objective-C,还可以通过不同的前端模块支持各种语言,包括 Java、Fortran、Ada、Pascal、Go 和 D 语言等等。
GCC 的编译过程可以划分为四个阶段:预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)以及链接(Linking)。

4个步骤
:预处理、编译、汇编、链接
| -E:仅执行预处理 # hello.i 源文件 i |
| -S:只编译 # hello.s 汇编文件 s |
| -c:编译和汇编,但不链接。 # hello.o 目标文件 o |
| -o <file>:指定输出文件。 # a.out 可执行文件 |
| -I: 指定头文件位置 # gcc -I ./inc hello.c -o hello |
| -g: 编译时增加调试文件 |
| -Wall: 显示所有警告信息 |
| -D: 向程序中“动态“注册宏定义 |
空间大,速度块,基本淘汰
| 1. gcc -c foo.c -o foo.o # 生成 foo.o 目标文件 |
| 2. ar rcs libfoo.a foo.o # 生成 libfoo.a 静态库 |
| 3. gcc hello.c -static libfoo.a -o hello # 编译 并链接静态库 libfoo.a |
| 4. gcc test.c -static ./lib/libmymath.a -I ./inc -o b.out # 文件+库+头文件 |
空间小,速度慢,最多使用
| 1. gcc -c add.c -o add.o -fPIC # 生成于位置无关的代码 -fPIC |
| 2. gcc -shared -o libmymath.so add.o sub.o # 制作动态库 |
| 3. -l: 指定库名 -L:指定库路径 |
| gcc test.c -lmymath -L./lib -I./inc -o a.out # 编译可执行程序 |
执行报错
原因
:
- 链接器:工作于链接阶段,工作时需要 -l 和 -L
- 动态链接器:工作于程序运行阶段,工作时需要提供动态库所在目录位置解决
解决
:
export LD_LIBRARY_PATH=动态库路径:$LD_LIBRARY_PATH
(一次进程里,临时)
移动到 /usr/lib
(永久)
.bashrc
中 写入 环境变量 (永久)
| vim /etc/ld.so.conf # 写入动态库的路径 |
| ldconfig # 刷新 (永久) |
ldd
是 Linux 系统下的一个命令行工具,用于查看可执行程序或共享库所依赖的动态链接库(Dynamic Linking Library,简称 DLL)。它会列出指定的可执行程序或共享库所使用的共享库,并显示它们的完整路径。
| root@freecho:/opt/C/gcc/lib/dynamicLib# ldd a.out |
| linux-vdso.so.1 (0x0000ffffb6b6d000) |
| libmymath.so => /lib/libmymath.so (0x0000ffffb6af0000) |
| libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 (0x0000ffffb6940000) |
| /lib/ld-linux-aarch64.so.1 (0x0000ffffb6b34000) |
| root@freecho:/opt/C/gcc/lib/dynamicLib# |
GDB 全称 “GNU symbolic debugger”,从名称上不难看出,它诞生于 GNU 计划(同时诞生的还有 GCC、Emacs 等),是 Linux 下常用的程序调试器。发展至今,GDB 已经迭代了诸多个版本,当下的 GDB 支持调试多种编程语言编写的程序,包括 C、C++、Go、Objective-C、OpenCL、Ada 等。实际场景中,GDB 更常用来调试 C 和 C++ 程序
| gcc gdbtest.c -o a.out -g # -g 使用该参数编译可执行文件,得到调试表 |
| gdb a.out # 开始 |
| l 1 # 从第一行开始显示 |
| l # 继续往下显示 (lsit) |
| b 52 # 52行设置断点 (breakout) |
| r # 运行 (run) |
| n # 逐过程单步执行,越过函数,系统函数用 n (next) |
| s # 逐语句单步执行,进入函数,其他情况与n无区别,执行下一步 (step) |
| until 16 # 运行程序直到达到指定的行号并停止 |
| p i # 打印变量 i 的值 (print) |
| continue # 继续执行断点后续指令 |
| quit # 退出gdb调试 |
| (gdb) b 23 # 设置断点 |
| (gdb) b 30 |
| (gdb) b 41 |
| (gdb) b 41 if i=5 # 设置条件断点 |
| (gdb) info b # 查看断点信息表 |
| Num Type Disp Enb Address What |
| 1 breakpoint keep y 0x00000000000009f8 in select_sort at gdbtest2.c:23 |
| 2 breakpoint keep y 0x0000000000000a7c in select_sort at gdbtest2.c:30 |
| 3 breakpoint keep y 0x0000000000000b14 in print_arr at gdbtest2.c:41 |
| 4 breakpoint keep y 0x0000000000000b14 in print_arr at gdbtest2.c:41 |
| stop only if i=5 |
| (gdb) delete 3 # 删除指定断点 |
| |
| delete # 删除所有断点 |
| disable # 禁用断点 |
| enable # 启用断点 |
- 使用
r
查找段错误出现位置
start
:用于开始执行程序并停在 main
函数的第一条语句上
finish
:当你在调试一个函数时,你可以使用 finish
命令来执行该函数的剩余部分,并停在函数调用的地方
ser args
:设置 main
函数命令行参数,在 start
run
命令之前
run 字符串1字符串2 ...
: 设置 main
函数命令行参数
info b
:查看断点信息表 information breakout
ptype i
:查看变量类型
backtrace
:查看函数的调用的栈帧和层级关系 缩写:bt
frame 编号
:切换函数的栈帧 缩写:f
display i
:设置跟踪变量
undisplay 编号
:取消跟踪变量
栈帧(Stack Frame)
是在程序执行期间,用于管理函数调用和局部变量的一种数据结构。在函数调用时,会创建一个栈帧来存储函数的参数、局部变量以及保存调用函数的返回地址和调用函数的寄存器状态等信息。
每个函数调用都会创建一个新的栈帧,将其添加到函数调用栈(Call Stack)的顶部。当函数执行结束后,其对应的栈帧会被销毁,栈帧也会被从调用栈中移除,程序会返回到调用该函数的上一个栈帧继续执行。
栈帧通常包含以下几个重要的部分:
- 返回地址:指向调用该函数的下一条指令的地址,用于函数执行完后返回到正确的位置。
- 函数参数:将函数的参数值存储在栈帧中,以便在函数内部使用。
- 本地变量:函数中定义的局部变量也被存储在栈帧中,它们只在函数执行期间存在,函数结束后将被销毁。
- 保存的寄存器状态:为了保护调用函数时的寄存器状态,可能需要将某些寄存器的值保存在栈帧中,在函数执行完后再恢复寄存器的值。
栈帧的使用使得函数调用和返回的过程更加高效和可控,同时提供了函数间局部变量的隔离,避免了函数之间的冲突。栈帧在计算机体系结构和操作系统中发挥着重要作用,为函数调用提供了必要的支持。
Makefile 是一个用于构建和管理项目的工程文件,它定义了项目中的源文件、依赖关系以及构建规则等信息,用于自动化编译和链接程序。
Makefile 通常使用 make 工具来执行其中定义的规则和命令。通过 Makefile,开发人员可以在项目中定义编译、链接、清理等操作的规则,以及指定项目中的文件之间的依赖关系。这样,当项目的源文件发生变化时,make 工具可以根据 Makefile 中的规则,自动检测变化并重新编译需要更新的文件,从而加快项目的开发和构建过程。
文件命令:makefile Makefile
- 目标的时间必须晚于依赖条件的时间,否则,更新目标
- 依赖条件如果不存在,找寻新的规则去产生依赖
ALL: 指定 makefile 的最终目标,否则最终目标写在文件开头
步骤
| root@freecho:/opt/C/make# vim makefile |
| root@freecho:/opt/C/make# cat makefile |
| hello:hello.c |
| gcc hello.c -o a.out |
| root@freecho:/opt/C/make# ls |
| hello.c makefile |
| root@freecho:/opt/C/make# make |
| gcc hello.c -o a.out |
| root@freecho:/opt/C/make# ls |
| a.out hello.c makefile |
makefile文件1
| ALL:a.out # 指定makefile的最终目标,否则最终目标写在文件开头 |
| |
| hell.o:hello.c |
| gcc -c hello.c -o hello.o |
| add.o:add.c |
| gcc -c add.c -o add.o |
| sub.o:sub.c |
| gcc -c sub.c -o sub.o |
| mul.o:mul.c |
| gcc -c mul.c -o mul.o |
| div1.o:div1.c |
| gcc -c div1.c -o div1.o |
| a.out:hello.o add.o sub.o mul.o div1.o |
| gcc hello.o add.o sub.o mul.o div1.o -o a.out |
两个函数分别为:wildcard(通配符)
和 patsubst(匹配替换)
2.1 $(wildcard ./*.c)
| src = $(wildcard *.c) |
| # src = add.c sub.c mul.c div1.c |
匹配当前目录下的所有后缀为 .c 的文件,将文件名组成列表,赋值给变量 src
2.2 $(patsubst %.c, %.o, $(src))
| obj = $(patsubst %.c, %.o, $(src)) |
| # obj = add.o sub.o mul.o div1.o |
将 参数3
中,包含 参数1
的部分,替换为 参数2
,即把 src 变量里所有后缀为 .c 的文件替换为 .o
makefile文件2、3
| src = $(wildcard *.c) |
| obj = $(patsubst %.c, %.o, $(src)) |
| |
| ALL:a.out |
| |
| hell.o:hello.c |
| gcc -c hello.c -o hello.o |
| add.o:add.c |
| gcc -c add.c -o add.o |
| sub.o:sub.c |
| gcc -c sub.c -o sub.o |
| mul.o:mul.c |
| gcc -c mul.c -o mul.o |
| div1.o:div1.c |
| gcc -c div1.c -o div1.o |
| a.out: $(obj) |
| gcc $(obj) -o a.out |
| |
| clean: # 终端输入 make clean 才会生效 |
| -rm -rf $(obj) |
步骤
clean: (没有依赖)
make clean -n
会演示清除命令,不会实际生效
make clean
则会实际生效
| root@freecho:/opt/C/make# make |
| gcc -c add.c -o add.o |
| gcc -c div1.c -o div1.o |
| gcc -c -o hello.o hello.c |
| gcc -c mul.c -o mul.o |
| gcc -c sub.c -o sub.o |
| gcc add.o div1.o hello.o mul.o sub.o -o a.out |
| root@freecho:/opt/C/make# ls |
| add.c add.o a.out div1.c div1.o hello.c hello.o m1 m2 makefile mul.c mul.o sub.c sub.o |
| root@freecho:/opt/C/make# make clean -n |
| rm -rf add.o div1.o hello.o mul.o sub.o |
| root@freecho:/opt/C/make# ls |
| add.c add.o a.out div1.c div1.o hello.c hello.o m1 m2 makefile mul.c mul.o sub.c sub.o |
| root@freecho:/opt/C/make# make clean |
| rm -rf add.o div1.o hello.o mul.o sub.o |
| root@freecho:/opt/C/make# ls |
| add.c a.out div1.c hello.c m1 m2 makefile mul.c sub.c |
$@
:在规则的命令中,表示规则中的目标
$<
:在规则的命令中,表示规则中的第一个条件,如果将该变量应用于 模式规则
中,它可将依赖列表中的依赖依次取出,套用规则模式
$^
:在规则的命令中,表示规则中的所有依赖条件,组成一个列表,以空格隔开,如果这个列表中有重复的项则消除重复
makefile文件4
| src = $(wildcard *.c) |
| obj = $(patsubst %.c, %.o, $(src)) |
| |
| ALL:a.out |
| |
| hell.o:hello.c |
| gcc -c $< -o $@ |
| add.o:add.c |
| gcc -c $< -o $@ |
| sub.o:sub.c |
| gcc -c $< -o $@ |
| mul.o:mul.c |
| gcc -c $< -o $@ |
| div1.o:div1.c |
| gcc -c $< -o $@ |
| a.out: $(obj) |
| gcc $^ -o $@ |
| |
| clean: |
| -rm -rf $(obj) |
编译 .c 文件 生成 .o 文件
makefile文件5
| src = $(wildcard *.c) |
| obj = $(patsubst %.c, %.o, $(src)) |
| |
| ALL:a.out |
| |
| a.out: $(obj) |
| gcc $^ -o $@ |
| |
| %.o:%.c |
| gcc -c $< -o $@ |
| |
| clean: |
| -rm -rf $(obj) a.out |
静态模式规则
表示对哪一个依赖条件套用这个规则
| $(obj):%.o:%.c |
| gcc -c $< -o $@ |
伪目标
声明了 .PHONY: clean ALL
,它告诉 Make 不会去检查是否存在名为 clean
或 ALL
的文件,而是直接执行它们后面的命令块。这样,运行 make clean
或 make ALL
时,Make 将执行相应的命令块。
最终形态 makefile
| src = $(wildcard *.c) |
| obj = $(patsubst %.c, %.o, $(src)) |
| |
| myArgs = -Wall -g |
| |
| ALL:a.out |
| |
| a.out: $(obj) |
| gcc $^ -o $@ $(myArgs) |
| |
| $(obj):%.o:%.c |
| gcc -c $< -o $@ $(myArgs) |
| |
| clean: |
| -rm -rf $(obj) a.out |
| |
| .PHONY: clean ALL |
相关参数
| -n:模拟执行make、make clean 命令 |
| -f:指定文件执行 make 命令 |
教程视频:Linux 系统编程哔哩哔哩 bilibili
Linux 系列文章:Linux – Echo (liveout.cn)
Linux 基础命令:Linux 学习资料分享 – Echo (liveout.cn)
Vim 配置:Linux 学习资料分享 – Echo (liveout.cn)
GitHub 仓库,包含教程讲义、代码以及笔记:https://github.com/PGwind/LinuxSystem
开始学cmake,但是makefile又忘记了,回来看看~
今天读了《CSAPP》的第七章:链接。感觉有了更深的理解。
《CSAPP》,即《深入理解计算机系统》是一本非常经典的著作,非常值得认真阅读!
动态库使用:网络编程时将错误检查封装成动态库从而规模化
在搞编译原理吗?
在学习linux编程,打算以后搞嵌入式软件~