Linux系统编程-基础
1- GCC编译
1.1 GCC编译四步骤:
预处理(Pre-Processing)
gcc -E hello.c -o hello.i
展开宏,头文件,替换条件编译,删除注释,空行,空白
编译(compiling) ----------消耗资源最多
gcc -S hello.i -o hello.s检查语法规范,将预处理后的文件转换为汇编语言代码
汇编(Assembling)
gcc -C hello.s -o hello.o将汇编语言代码翻译成二进制形式的指令和数据,这些指令和数据可以直接在计算机上执行。
链接(Linking)
gcc hello.o -o hello
GCC将目标文件与其他必要的库文件进行链接,生成最终的可执行文件。链接器会解析目标文件之 间的引用和符号,将它们连接在一起,生成一个完整的可执行程序。这个阶段还会处理一些额外 的任务,如符号重定位、库函数的解析等。

1.2 GCC编译常用参数
GCC编译器提供了许多参数和选项,用于控制编译过程和生成的可执行程序的行为。下面是一些常用的GCC编译参数和选项:
-c:只进行编译,生成目标文件,不进行链接操作。-o <file>:指定输出文件的名称。-g:生成调试信息,以便在调试程序时使用。-Wall:打开所有警告信息。-Werror:将警告视为错误,编译过程中出现警告将导致编译失败。-O<level>:指定优化级别,其中<level>可以是0、1、2、3或s。较高的级别会进行更多的优化,但编译时间可能会更长。嵌入式编程需要小心代码被优化掉-std=<standard>:指定所使用的C或C++标准,如-std=c11表示使用C11标准,-std=c++17表示使用C++17标准。-I<dir>:添加头文件搜索路径。-L<dir>:添加库文件搜索路径。-l<library>:链接指定的库文件。-D<macro>:定义一个宏。-U<macro>:取消定义一个宏。-E:只进行预处理操作,生成预处理后的文件。-S:只进行编译和汇编操作,生成汇编语言文件。-shared:生成共享库(动态链接库)。-static:生成静态链接可执行程序。-pedantic:启用更严格的标准检查。-pthread:链接线程库。-M:生成.c文件与头文件依赖关系用于Makefile,包括系统库的头文件。生成的依赖关系文件通常以.d为扩展名,可以使用-MF参数指定生成的文件名。-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编译出错
collect2: error : ld returned 1 exit status
链接器出错。collect2为完成链接工作的链接器,ld调用链接器驱动链接器工作。
2- 静态库和动态库
在使用GCC编译器时,可以使用-static参数生成静态链接的可执行文件,使用-shared参数生成动态链接的共享库(动态库)。
2.1 静态库
静态库是一组编译好的目标文件(通常是
.o文件)的集合,它们被打包成一个单独的文件。在链接时,静态库的目标代码会被完整地复制到可执行文件中,使得可执行文件在运行时不再依赖于静态库。
静态库的优点是简单易用,只需将库文件链接到可执行文件中即可,无需考虑库的加载和依赖关系。
缺点是静态库会增加可执行文件的大小,而且如果多个可执行文件都使用同一个静态库,会造成代码的冗余。
2.2 动态库
动态库是一组编译好的目标文件的集合,它们被打包成一个单独的文件,但在运行时并不会被完整地复制到可执行文件中。
在链接时,可执行文件只包含对动态库的引用,而不是实际的目标代码。动态库会在运行时被动态加载到内存中,并与可执行文件共享。
动态库的优点是可以被多个可执行文件共享,减少了重复代码的存储空间。同时,如果动态库发生更新或修复,只需替换动态库文件即可,无需重新编译可执行文件。
缺点是动态库的使用稍微复杂一些,需要确保库文件在运行时能够被正确地加载和找到。
总结来说,静态库适用于简单的项目或者需要独立性较高的可执行文件,而动态库适用于多个可执行文件共享代码和灵活更新的情况。静态库加载快,动态库加载慢!
2.3 静态库制作
编写源代码:首先,编写你的库的源代码文件(通常是
.c或.cpp文件)。编译源代码:使用GCC编译器将源代码编译为目标文件(
.o文件)。例如,使用以下命令编译一个源文件mylib.c:gcc -c mylib.c -o mylib.o这将生成一个名为
mylib.o的目标文件。打包目标文件:使用
ar命令将目标文件打包成静态库。例如,使用以下命令将目标文件mylib1.o和mylib2.o打包成静态库libmylib.a:(静态库命名规则需要以.a作为文件后缀,以lib开头)ar rcs libmylib.a mylib1.o mylib2.o这将生成一个名为
libmylib.a的静态库文件。r选项用于向库中添加文件或更新现有文件。c选项用于创建库文件,如果库文件不存在则创建一个新的库文件。s选项用于创建索引表,提高链接速度。
创建静态库头文件:创建文件
mylib.h,用来在里面声明函数,并在其他代码源文件中#include该头文件#ifndef MY_HEADER_FILE_H // 防止头文件重复包含 #define MY_HEADER_FILE_H /***** 头文件内容 *****/ #endif使用静态库:现在可以将生成的静态库文件
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参数)
编写源代码:首先,编写你的库的源代码文件(通常是
.c或.cpp文件)编译源代码:使用GCC编译器将源代码编译为目标文件(
.o文件)。例如,使用以下命令编译一个源文件mylib.c:gcc -c -fPIC source1.c -o source1.o gcc -c -fPIC source2.c -o source2.o ...创建动态库:使用
gcc将目标文件链接成动态库文件,可以使用以下命令:(动态库命名规则需要以.so作为文件后缀,以lib开头)gcc -shared source1.o source2.o ... -o libmylib.so安装动态库(可选):将生成的动态库文件安装到系统的库目录中,以便其他程序可以使用。可以使用以下命令:
sudo cp libmylib.so /usr/local/lib sudo ldconfig使用动态库
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 环境变量来指定动态库的搜索路径,例如:
通过环境变量
export LD_LIBRARY_PATH=/path/to/library:$LD_LIBRARY_PATH # 可以保留原有的LD_LIBRARY_PATH值
export LD_LIBRARY_PATH=/path/to/library # 直接替换覆盖原有的LD_LIBRARY_PATH值永久生效
写入终端配置文件 然后 .bashrc 建议使用绝对路径
1) vim ~/.bashrc
2) 写入 export LD_LIBRARY_PATH = /path/to/library , 保存
3) . .bashrc 或者 source .bashrc 或者 重启终端 ---> 让修改后的.bashrc生效将库文件拷贝到标准c库目录下,通常为 /lib下
配置文件法
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 breakpointsframe/f: 切换函数的栈帧set: 设置变量的值 set var n=100run 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 helloALL:指定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 -l4.5.3 自动变量
$@ 表示依赖列表中的文件名
$^ 表示依赖列表中的所有文件名, 组成一个列表, 以空格隔开, 如果这个列表中有重复的项则消除重复项
$< 表示依赖列表中的第一个文件名,如果将该变量应用在模式规则中,可以将依赖列表中的依赖依次取出,套用模式规则
$? 所有在依赖列表里面比当前目标新的文件名,用空格隔开;
$+ 与$^相似,也是依赖列表中的文件名用空格隔开,不同的是这里包含了所有重复的文件名;
$* 显示目标文件的主干文件名,不包含后缀部分。特别注意:自动变量只能在规则的命令位置中使用.
4.6 函数
4.6.1 wildcard函数
wildcard – 查找指定目录下的指定类型的文件
src=$(wildcard *.c) //找到当前目录下所有后缀为.c的文件,将文件名组成列表赋值给src4.6.2 patsubst函数
patsubst – 匹配替换
obj=$(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o4.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)
- 感谢你赐予我前进的力量

