GCC、静态库
- 1.2 GCC(1)gcc
- (1)常用命令
- (2) C程序编译过程
- (3)GCC工作流程
- 1.3 GCC(2)g++
- 1.3静态库的制作
- 1.5静态库的使用
- 1.6动态库的制作
- 1.7动态库加载失败的原因
- 1.8解决动态库加载失败的原因(用add把动态库加载到内存中去)
- 1.9静态库与动态库对比
- 1.10MakeFile
- 1.13 GDB调试
- 1.13.1 GDB准备、启动、退出、查看代码
- 1.13.2 GDB断点操作
- 1.13.3 GDB利用断点查看数值
1.2 GCC(1)gcc
(1)常用命令
1、ctrl+l 清空屏幕
2、使用gcc编译
gcc xx.c -o app
解释:gcc + 文件名 + -o(用于指定编译后的文件名) + app(文件名)
3、生成可执行文件
./app
解释:运行刚刚编译出来的文件
如果gcc xx.c -o app不加-o,只有gcc xx.c
那么会生成一个a.out的可执行程序
(2) C程序编译过程
高级语言 编译 成汇编语言
汇编语言 汇编 成机器语言
机器语言 运行
(3)GCC工作流程
预处理:
1、展开头文件,将头文件中的内容复制进源代码
2、删除注释
3、对定义的宏做宏替换
指令: gcc test.c -E -o test.i
编译器:
将源代码编译成汇编代码(.s)
gcc test.i -S -o test.s
直接执行gcc test.c -S也会生成.s文件(就包含了预处理这一步)
汇编器:
将汇编代码汇编成目标代码(.O)
gcc test.s -c -o test.o
链接:
-c 生成目标代码 test.o,test.o 不是一个可执行程序。如果下一步需要继续链接成可执行程序,需要输入指令 gcc test.o -o test.out(这里直接将 .o 目标文件链接成可执行程序 test.out,可以有多个 .o 文件,这里只有一个)
所以整个直接 gcc test.c 包含了预处理,编译,汇编,链接成可执行文件4步。
1.3 GCC(2)g++
别管那么多,c就用gcc,cpp就应g++
-D使用示例:
#include <stdio.h>
#define PI 3.14
int main(){
int a = 10;
#ifdef DEBUG
printf("我不会爬树\n");
#endif // DEBUG
for(int i = 0; i < 3; ++i){
printf("hello,GCC\n");
}
return 0;
}
-Wall使用示例:
1.3静态库的制作
静态库实际上是将.c文件们打包。这些.c文件定义的函数,都在.h文件中申明。这样只需要把静态库和头文件给用户,用户就可以知道那些函数可以调用,但不清楚函数的具体实现。
-c使用示例:(生成.o文件)
制作静态库之前,首先需要把.c文件用gcc命令和 -c选项做成.o文件。 src目录下:gcc -c add.c div.c mult.c sub.c 是要报错的,因为找不到.h文件。如下所示。
所以实际上还要加上-I(大写i)选项,后面跟上.h所在的文件夹路径,告诉编译器去这个路径下找头文件:
gcc -c add.c div.c mult.c sub.c -I …/include/
注:.c文件在-c选项的前面还是后面无所谓
gcc add.c div.c mult.c sub.c -c -I …/include/
也可以。
ar命令的使用:(将.o文件打包成静态库)
ar rcs libsuanshi.a add.o div.o sub.o mult.o
移动命令:mv + 文件名 + 目的文件夹路径
1.5静态库的使用
参数选项
1、-I(大写i) :查找头文件的路径 (后面跟目录)
使用: -I 路径
例如:gcc main.c -o app -I ./include/
此时只找到了头文件,但没找到这些函数的定义,也就是静态库
2、-l(小写L):指定加载哪个库(后面跟的是库名字,不是库的文件名)
文件名:libcala.a
库名:calc
3、-L:指定到哪个文件夹下去找静态库(后面跟目录)
示例:
gcc main.c -o app -I ./include/ -l calc -L ./lib/
1.6动态库的制作
什么叫:与位置无关?
“与位置无关”(Position-Independent Code,简称PIC)的代码是一种可执行代码,它在内存中的位置不影响其执行。换句话说,它可以在任何地方执行,而不需要修改。
这是通过使所有代码引用都通过间接寻址实现的,如通过全局偏移表(Global Offset Table,简称GOT)。例如,不是直接引用数据或函数的绝对地址,而是从GOT中查找这些地址。
这种方法的主要好处之一是,可以创建可以在多个程序之间共享的库。这就是所谓的动态链接库或共享库。因为每个程序可以将库加载到内存中的不同位置,而库代码的执行不受其在内存中的实际位置影响,因此可以共享。
如果你在用gcc编译代码时,可以使用-fPIC或-fpic选项来生成与位置无关的代码。这两个选项之间的区别在于,-fpic生成的代码使用的GOT较小,因此可能在大型程序中无法使用,而-fPIC则没有这个限制,但生成的代码可能会稍大一些。在大多数情况下,建议使用-fPIC选项。
1、制作动态库:
生成与位置无关的.o文件
gcc -c -fpic add.c div.c sub.c mult.c (如果头文件不在,记得指定头文件文件夹 -I …/include/ )
生成动态库
gcc -shared add.o sub.o mult.o div.o -o libcalc.so
2、使用动态库
与使用静态库一样,要指定头文件所在路径。动态库所在路径以及要使用的动态库名字(不是文件名字)
gcc main.c -o main -I ./include/ -l calc -L ./lib/
但这样使用会报错,运行main这个可执行文件的时候找不到动态库文件
1.7动态库加载失败的原因
原因:gcc在链接时动态库代码不会被打包进可执行文件中,只会放入一些相关信息。所以在程序运行以后,需要通过系统的动态载入器找到动态库的绝对路径(找的顺序如下图所示),然后把动态库加载到内存中。如果我们没给这个绝对路径,那就会找不到动态库。
ldd:list dynamic dependendcies 列出动态依赖库
后面的一串数组是内存地址,此时可以看到 libcalc.so这个动态库没有被找到。
每个程序就是一个进程,linux会为每个进程分配虚拟地址空间(DT_RPATH段)
1.8解决动态库加载失败的原因(用add把动态库加载到内存中去)
1、配置环境变量
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wcx/linux/lesson06/library/lib(冒号后面是动态库的绝对路径)
查看是否配置成功
1)echo $LD_LIBRARY_PATH
回车以后会显示绝对路径
2)ldd + 可执行文件
会列出该文件需要的动态库以及各个库的位置
弊端
在终端的环境变量里配置只是临时的,一旦开一个新终端就失效了
2、用户级别的配置
配置.bashrc文件,在home目录下(~)
vim打开
shift+G跳到最后一行
o 往下插入一行
输入配置路径:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wcx/linux/lesson06/library/lib
esc退出编辑模式,shift+:后,wq保存并退出
配置完成后还需要使文件生效 . .bashrc。第一个点相当于source,所以写成 source .bashrc也可以
这样配置好的动态库可以直接运行可执行文件,不必再指定头文件和库文件的位置。
3、系统级别的配置
需要管理员权限
sudo vim /etc/profile
到最后一行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/wcx/linux/lesson06/library/lib
保存退出
配置完成后还需要使文件生效 . /etc/profile。第一个点相当于source,所以写成 source /etc/profile也可以
以上两种配置环境变量的方式打开新的终端可能还要source以下文件才能生效。同样,如果删除这条环境变量,需要开一个新终端才能生效。
4、配置 /etc/ld.so.cache文件
该文件是二进制文件,无法直接输入
所以要通过sudo vim /etc/ld.so.conf文件配置
进入文件后,将绝对路径复制进去就行/home/wcx/linux/lesson06/library/lib
更新:sudo ldconfig ( ldconfig第一个字母是小写L)
5、把文件放到 /lib/ 或者 /user/lib目录下
但是不建议,因为里面本身就包含了许多系统自带的库文件,如果自己写得库文件和系统重名,有可能会被替换,很危险。
1.9静态库与动态库对比
总得来说:库比较小就用静态库,比较大就用动态库
发布程序无需提供静态库,移植方便
打包成可执行程序以后直接把可执行程序给别人就可以,不用给静态库,因为静态库已经复制进去了。
消耗系统资源,浪费内存
多个程序使用同一个静态库的时候,每一个程序都会把静态库复制进去
更新、部署、发布麻烦
由于每个程序之间相互独立,如果静态库做修改,那么所有涉及到这个静态库的程序都要重新编译
1.10MakeFile
1、创建MakeFile文件示例
以该文件夹下文件为例:
1)vim Makefile 创建新文件并进入
2)编写Makefile
3)执行make命令
会自动找到当前目录下的那个Makefile文件
命令在执行之前,需要先检查规则中的依赖是否存在
如果存在,执行命令
如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的如果找到了,则执行该规则中的命令
比如这里第一条规则要找.o文件
不存咋,所以往下看其他规则,先把.c文件生成成.o文件
此外,由于MakeFile中其他规则都是为第一条规则服务的,如果与第一条规则无关,那就不会被执行。比如最后一行有个
b.o:b.c
gcc -c b.c -o b.o
跟第一行规则无关,就不会生成b.o文件
检测更新,在执行规则中的命令时,会比较目标和依赖文件的时间
如果依赖的时间比目标的时间晚,需要重新生成目标
如果依赖的时间比目标的时间早,目标不需要更新,对应规则中的命令不需要被执行
现在,在第一条规则中找依赖。发现4个.o文件都没有,于是会向下找规则来生成.o文件。四个.o文件其实用的规则是一样的,所以我们可以用模式匹配。首先找sub.o文件,发现下面有规则适用,于是调用。然后找add.o发现这个规则也适用,于是继续调用。
$(函数名 参数)
上图中,wildcard就是函数名 wildcard:未知数
示例中:.c ./sub/.c 是两个目录,意思是要获取当前目录下的所有.c文件,以及当前目录下的sub目录下,所有.c文件。多个目录之间用空格隔开
关于伪命令
clean:
rm &(objs) -f
这条规则其实不生成任何文件
但如果在当前目录下有了一个clean文件,在执行这条命令的时候就会比较依赖和目标的前后。由于没有依赖,目标就会是最新的,这个命令就会一直不执行。
所以要在前面加上 .PHONY,说明他后面跟的目标都被是伪目标。这样即使有clean文件也不影响这个命令的执行。
注:
在执行make的时候,由于clean和第一条规则无关,所以不会自动执行,要 make clean才会执行
1.13 GDB调试
1.13.1 GDB准备、启动、退出、查看代码
1、准备工作:使用-g选项生成带源码信息的可执行程序
可以看到,test文件大于test1文件,说明-g选项添加一些源码信息到可执行文件中。
注:-h选项是在列出文件信息的时候,把文件大小转成更容易阅读的K,KB ,G等大小
2、启动调试
gdb+可执行程序
3、给程序设定参数
test.c文件中可以看到,main函数是有参数的
设置参数:set args +参数
显示参数:show args
在下一行就会有:程序开始调试时的参数列表是:10 204、退出gdb调试模式
直接写quit或者q就可以
5、help
在调试模式下,键入help,help all是显示所有帮助信息,可以回车显示下一屏。
如果需要有针对显示 可以写 help set
那么就会显示所有set开头的命令,比如 set args
5、查看代码
vim test.c
set nu 显示行号
也可以在gdb里面查看
1)list就可以,一次显示10行,回车或者list显示下面10行
2)list + 行号:list 20 就会把20作为中间行,显示15-25行
3)list+函数名:list main 会把main函数开始的那一行作为中间行显示出来
4)有时候可执行文件不会只有一个.c文件生成
此时直接输入list或者l,默认显示main函数所在的文件
如果查看非当前文件代码:
list/1 文件名:行号:list bubble.cpp:1
list/1 文件名:函数名:list select.cpp:selectSort
6、设置显示的行数
show list/listaize
set list/lintaize 行数
1.13.2 GDB断点操作
在设置断点的时候,和list一样,如果不指定文件,断点就打在main函数所在的文件里
删除断点
使断点无效
设置条件断点
只有当i=3时,才会在16行停住。
1.13.3 GDB利用断点查看数值
1、运行GDB程序
start:程序停在第一行
run(遇到断点才停)
2、继续运行,到下一个断点停
c/continue
要是接下来没有断点,就会到程序结尾
打一些断点:
run 跑到第一个断点处
c 继续跑直到下一个断点。断点2是bubble文件的bublesort函数,所以会落到这个函数内的第一行,也就是for。list以下就能看到这个文件的第八行确实是函数内的第一行for
第三个断点在main的16行,是在一个for循环内
打印变量名
第一次打印i是2,continue(c),跑到下一个断点(因为在for循环内所以又到16行),此时再打印i是3.
打印变量类型
跳出循环
1、循环内不能有断点。
2、执行until,此时程序会一直执行,直到不再能够进入函数体
3、要连续两次until。并且循环内至少执行一次才能跳出循环
一开始 i = 2时,执行until,就会一直跑到 i = len。此时再执行step,就会到cout<<endl
next与step的区别
next遇到函数不会进入
step碰到函数会进入
注意:
如果想用finish跳出函数,函数体内不能有断点
自动变量操作
用test做测试
如果想查看a和b的值,每一次都print的话太麻烦,就可以设置的自动变量
display a
这样接下来每操作一次,都会自动输出a的值
还可以查看设置了哪些自动变量:info display
如果想取消自动变量:undisplay 编号 就行
设置变量值 set var
这里i走到3,如果想从i = 9开始走
就可以set var i= 9