
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 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)
- 感谢你赐予我前进的力量