GCC,GDB,Makefile的quickstart
快速入门后最好是看官方文档系统学习
GCC
When you invoke GCC, it normally does preprocessing, compilation, assembly and linking.
四个过程:
- 预处理
gcc -E test.c -o test.i
将.c .h
文件预处理为.i
文件 C/C++的语言代码 - 编译
gcc -S test.i -o test.s
生成汇编文件.s
汇编语言代码 - 汇编
gcc -c test.s -o test.o
将汇编文件编译为目标文件.o
.obj
二进制 - 链接
gcc test.o -o test
生成可执行文件.exe
.elf
直接生成可执行文件:
1 | gcc test.c -o test //如果未指定输出文件名称,默认输出为a.out |
-O
编译优化,有四个级别0~3,级别越高优化越好,但编译时间越长,中间代码可读性越差。0级无优化效果:gcc -O1 test.c -o test
-I
指定头文件所在文件夹:
- 如果文件不同在一个目录下,可以使用
-I
指定文件夹,如test.h
在include
中 - 如:
gcc test.c -I ./include -o test
-D
编译时定义宏
-Wall
提示警告信息
-g
加调试信息
-L
指定库所在文件夹
-l
指定要连接的库名
-m32
或-m64
编译出32位或64位程序
库的制作
静态库
命名格式:lib
+静态库名
+.a
,如libsort.a
制作步骤:(示例将a.c b.c c.c
制作静态库libmytest.a
)
- 生成对应的
.o
文件,gcc a.c b.c c.c -c
- 得到静态库:
ar rcs libmytest.a a.o b.o c.o
- 打包
.o
文件的过程 ar
工具包不包含在gcc
中r
—> 将文件插入静态库中c
—> 创建静态库,不管库是否存在s
—> 写入一个目标文件索引到库中,或者更新一个存在的目标文件索引nm libmytest.a
查看库中的符号(函数,全局变量等)
- 打包
使用静态库的方法:
gcc 源文件 -L静态库路径 -l静态库名 -I头文件目录 -o 可执行文件名
如:
gcc main.c -L./ -lmytest -I./ -o app
静态库名要去电前缀
lib
和后缀.a
gcc 源文件 -I头文件 libxxx.a
libxxx.a
也可以是路径,如:gcc main.c lib/libMyCalc.a -Iinclude -o app
生成的静态库需要跟对应的头文件同时发布,头文件存放的是函数接口(函数声明)
优点:
- 寻址方便,速度快
- 库被打包到可执行程序中,直接发布可执行程序即可使用
缺点:
- 静态库的代码在编译过程中已经被载入可执行程序,因此体积较大
- 如果静态函数库改变了,那么你的程序必须重新编译
使用场合:
- 在核心程序上使用,保证速度,可忽视空间
- 主流应用于80、90年代,现在很少用
动态库/共享库
命名格式:lib
+静态库名
+.so
,如libsort.so
,win下是.dll
文件
制作:
- 生成“与位置无关”的目标文件:
gcc -fPIC a.c b.c c.c -c
- 制作动态库:
gcc -shared -o libmytest.so a.o b.o c.o
使用动态库:
gcc main.c lib/libMyCalc.so -o app -I./include
gcc main.c -L./ -lMyCalc -o app -I./include
执行生成的可执行文件:./app
—> 运行失败
报错:./app: error while loading shared libraries: lib/libMyCalc.so: cannot open shared object file: No such file or directory
问题原因:
- 查看依赖的共享库:
ldd app
发现libMyCalc.so
找不到 - 没有给动态链接器(
ld-linux.so.2
)指定好动态库libMyCalc.so
的路径
解决:
法1) 直接将 libMyCalc.so
文件拷贝到/usr/lib/
或 /lib
目录下,不推荐
法2)临时设置:export LD_LIBRARY_PATH=库路径
,将当前目录加入环境变量,但是终端退出了就无效了。
法3)将export LD_LIBRARY_PATH=库路径
写入~/.bashrc
文件中,需要重启终端,永久有效
法4)动态链接器的配置文件 —> /etc/ld.so.conf
,将动态库的绝对路径(如:/home/yang/test/lib
)写进去,更新:sudo ldconfig -v
GDB
GDB: The GNU Project Debugger (sourceware.org)
基本命令
若要使用gdb调试程序,在编译源代码时必须带上-g
参数 :gcc -g hello.c -o hello.out
进入gdb交互界面:gdb .out文件
或 gdb
进入环境之后 file 文件
加载文件
- 没有调试信息
Reading symbols from base64...(no debugging symbols found)...done.
- 可以调试
Reading symbols from test.out...done.
也可以使用readelf -S 文件名 | grep debug
查看是否能调试
0)set args
可指定运行时参数。(如:set args 10 20 30 40 50 )
show args
命令可以查看设置好的运行参数pwd
显示当前所在目录
1)start
运行程序,停在第一执行语句
2)run r
运行程序
3)break b
打断点 行号 或 函数名 或 文件名:行号
b 12
b main
b test.c:main
b class::function
在类class的function函数的入口处停住b namespace::class:;function
在名称空间为namespace的类class的function函数的入口处停住b 12 if num>0
# 设置程序在断点停止的条件info breakpoints
查看所有断点,会显示断点编号Num
4)delete 1
删除编号为1的断点
5)disable [range...]
dis
disable所指定的停止点,如果什么都不指定,表示disable所有的停止点
6)enable [range...]
ena
enable所指定的停止点,如果什么都不指定,表示enable所有的停止点
7)print p
打印变量的值,或变量的地址
print a
查看变量a的值print &a
查看变量a的地址print *a
查看指针a指向的内容print 'test.c'::i
print 'sum'::i
8)ptype
打印变量的类型
9)continue c
从断点处继续运行
10)next n
单步调试(逐过程,函数直接执行)
11)step s
单步执行(逐语句,进入函数内部)
12)list l
查看源代码,当前行,上2行下8行
list -
显示当前行前面的源程序 ,上5行下5行list 6,21
列出6到21行的代码list test.c:main
列出test.c文件的main函数set listsize count
设置一次显示源代码行数,默认10行show listsize
查看当前listsize
设置
13)quit q
退出gdb交互界面
14)help [name]
查看命令的帮助
15)info
查看信息
info breakpoints
查看断点信息
16)display
追踪变量的值,当程序停住时,或是在你单步跟踪时,这些变量会自动显示
info display
查看所有追踪的变量及对应Num
undisplay Num
或delete display Num
取消追踪某个变量disable display num
不显示追踪的变量enable display num
显示追踪的变量
17)until
u
跳出循环 类似break
18)finish
跳出函数
19)set var v=值
修改程序中变量的值,v是程序的变量名
2、调试core文件
编译没有问题,运行程序崩溃
1)先修改shell限制
ulimit -c unlimited
2)更改core dump
生成路径
core dump默认会生成在程序的工作目录,但是有些程序存在切换目录的情况,导致core dump生成的路径没有规律
示例:
1 | echo /data/coredump/core.%e.%p> /proc/sys/kernel/core_pattern |
- 更改core文件生成路径,放在
/date/coredump
文件夹中(要先创建文件夹) %e
表示程序名,%p
表示进程id
不能生成core文件,https://blog.csdn.net/qq_35621436/article/details/120870746
1 | $ gdb [exec file] [ core file] # 如 gdb ./test test.core |
- 再输入
where
或backtrace
命令,gdb就会输出coredump的位置
3、调试正在运行的程序
后台运行 ./test.out &
有输出的话:nohup ./test.out &
查看进程PID ps -ef | grep 文件名
gdb -p 进程号
可能需要root权限
GDB调试指南(入门,看这篇够了)_程序猿编码的博客-CSDN博客_gdb调试
父子进程追踪
使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child
命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent
设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。
Makefile
概述 — 跟我一起写Makefile 1.0 文档 (seisman.github.io)
跟我一起写 Makefile(完整版)_FixCarMaster的博客-CSDN博客_跟我一起写makefile
安装sudo apt install make
文件命名:Makefile
或 makefile
三要素:目标、依赖、命令
注意:最终目标要写在最上面
注释:#
执行:make
—> 通过makefile生成目标文件
make
使用makefile文件make -f mm
指定一个名字不为makefile的文件make clean
—> 清除编译生成的中间.o
文件和最终目标文件- 如果当前目录下有同名clean文件,该命令不执行
- 在命令前面加
-
:表示此条命令出错,make也会继续执行后续的命令。如:-rm a.o b.o
格式:
1 | 目标:依赖 |
简单样例
1 | app:main.c sub.c add.c mul.c |
如果某个文件修改,要重新编译,当文件很多时很耗时。修改:
1 | app:main.o add.o sub.o mul.o |
当某个.c
文件改变时,执行make
只会对应生成.o
及最后的链接,其它未修改的文件不会再次生成,节省了编译的时间。
原理
若想生成目标,检查规则中的依赖条件是否存在,如果不存在,寻找是否有规则用来生成该依赖文件
检查规则中的目标是否需要更新,必须检查它的所有依赖,依赖中有任意一个被更新,则目标必须更新(判断条件:依赖文件比目标文件时间晚)
伪目标
1 |
|
示例:clearAll
和 clear
就是伪目标
1 | # 注释 |
使用Makefile
1 | $ make hello |
变量
- 赋值
=
如:obj = a.o b.o c.o
- 追加
+=
如:obj += d.o
- 常量
:=
如:target := app
- 使用:
$(变量名)
1 | # Makefile |
Makefile 维护的一些变量:
- 通常格式都是大写,部分有默认值。如:
CC
:默认值cc
意思即gcc
CPPFLAGS
: 预处理器需要的选项。如:-I
CFLAGS
:编译的时候使用的参数–Wall –g -c
LDFLAGS
:链接库使用的选项–L
-l
- 用户可以修改这些变量的默认值。如:
CC = gcc
模式匹配:
%.c
%.o
任意的.c
或.o
文件 *.c *.o
所有的 .c .o
文件
自动变量:
$<
规则中的第一个依赖$@
规则中的目标$^
规则中所有依赖
1 | app:main.o sub.o mul.o |
函数
makefile中所有的函数必须都有返回值
wildcard
:查找指定目录下指定类型的文件,一个参数
src = $(wildcard ./src/*.c)
查找当前目录的src
文件夹下的.c
文件
patsubst
:匹配替换,从src中找到所有.c 结尾的文件,并将其替换为.o
obj = $(patsubst %.c ,%.o ,$(src))
把src变量中所有后缀为.c
的文件替换成.o
ob = $(patsubst ./src/%.c, ./obj/%.o, $(src))
指定.o
文件存放的路径./obj/%.o
1 | target = app |