OS:ubuntu16.04
1.GCC编译器
gcc(GNU Compiler Collection,GNU 编译器套件),是由 GNU 开发的编程语言编译器。gcc原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,gcc同样适用于微软的Windows。
gcc最初用于编译C语言,随着项目的发展gcc已经成为了能够编译C、C++、Java、Ada、fortran、Object C、Object C++、Go语言的编译器大家族。
基本语法:
gcc [-option] <filename>
gcc常用选项 | 作用 |
---|---|
-o file | 指定生成的输出文件名为file |
-E | 只进行预处理 |
-S(大写) | 只进行预处理和编译 |
-c(小写) | 只进行预处理、编译和汇编 |
-v / --version | 查看gcc版本号 |
-g | 包含调试信息 |
-On n=0~3 | 编译优化,n越大优化得越多 |
-Wall | 提示更多警告信息 |
-D | 编译时定义宏 |
示例:
#准备一个测试用的hello.c文件
#分步编译:预处理 -> 编译 -> 汇编 -> 链接
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello
#一步编译:
gcc hello.c -o hello
#如果不用 -o 选项指定输出文件名字, gcc编译器会生成一个默认的可以执行a.out。
#显示所有的警告信息:
gcc -Wall test.c
#将警告信息当做错误处理:
gcc -Wall -Werror test.c
#编译时定义宏:
#准备测试文件:printf("SIZE: %d\n", SIZE);
gcc test.c -DSIZE=10
2.静态链接和动态链接
2.1 静态链接
由链接器在链接时将库的内容加入到可执行程序中。
优点:
- 对运行环境的依赖性较小,具有较好的兼容性
缺点:
- 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间
- 库函数有了更新,必须重新编译应用程序
2.2 动态链接
链接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。
优点:
- 在需要的时候才会调入对应的资源函数
- 简化程序的升级;有着较小的程序体积
- 实现进程之间的资源共享(避免重复拷贝)
缺点:
- 依赖动态库,不能独立运行
- 动态库依赖版本问题严重
2.3 对比
#创建一个简单的测试文件test.c
#动态链接(默认为动态链接):
gcc test.c -o test_dynamic
#静态链接:
gcc -static test.c -o test_static
#完成后,使用ll命令查看两个文件大小,静态链接的文件要远大于动态链接的文件。
3.静态库和动态库
3.1 静态库的制作
静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。
注意:
静态库的命名一般分为三个部分:
- 前缀:lib
- 库名称:自己定义
- 后缀:.a
所以最终的静态库的名字应该为:libxxx.a
步骤:
-
准备一些放进库中的测试文件。
add.h:
#ifndef __ADD_H__ #define __ADD_H__ int add(int x, int y); #endif /*__ADD_H__*/
add.c
#include "add.h" int add(int x, int y) { return x + y; }
类似可以完成减法,乘法,除法。
-
将c源文件生成对应的.o文件。
gcc -c add.c -o add.o gcc -c sub.c -o sub.o gcc -c mul.c -o mul.o gcc -c div.c -o div.o
-
使用打包工具ar将准备好的.o文件打包为.a文件。
ar -rcs libtest.a add.o sub.o mul.o div.o #r:更新 #c:创建 #s:建立索引
3.2 静态库的使用
静态库制作完成之后,需要将.a文件和头文件一起发布给用户。
-
编写一个测试文件test.c,内容如下:
#include <stdio.h> #include "add.h" #include "sub.h" #include "mul.h" #include "div.h" int main(void) { int x = 18; int y = 23; printf("x + y = %d\n", add(x, y)); printf("x - y = %d\n", sub(x, y)); printf("x * y = %d\n", mul(x, y)); printf("x / y = %d\n", mydiv(x, y)); return 0; }
-
将拿到的.a文件和头文件放在和测试文件的同一目录下。
-
编译,然后就可以运行了
gcc test.c -L./ -I./ -ltest -o test #-L:表示要连接的库所在目录 #-I:表示指定头文件的目录为当前目录 #-l(小写L):指定链接时需要的库,去掉前缀和后缀
3.3 动态库的制作
共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。
动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
注意:
共享库的命名一般分为三个部分:
- 前缀:lib
- 库名称:自己定义即可
- 后缀:.so
所以最终的动态库的名字应该为:libxxx.so
步骤:
-
准备一些放进库中的测试文件。
add.h:
#ifndef __ADD_H__ #define __ADD_H__ int add(int x, int y); #endif /*__ADD_H__*/
add.c
#include "add.h" int add(int x, int y) { return x + y; }
类似可以完成减法,乘法,除法。
-
生成目标文件。
gcc -fPIC -c add.c gcc -fPIC -c sub.c gcc -fPIC -c mul.c gcc -fPIC -c div.c #-fPIC:创建与地址无关的编译程序(pic,position independent code),为了能够在多个应用程序间共享。
-
生成共享库
gcc -shared add.o sub.o mul.o div.o -o libtest.so #-shared:指定生成动态链接库
-
通过nm命令查看对应的函数
nm libtest.so | grep add nm libtest.so | grep sub
ldd命令:查看可执行文件的依赖的动态库
3.4 动态库的使用
-
编写测试文件test.c,内容如下:
#include <stdio.h> #include "add.h" #include "sub.h" #include "mul.h" #include "div.h" int main(void) { int x = 18; int y = 23; printf("x + y = %d\n", add(x, y)); printf("x - y = %d\n", sub(x, y)); printf("x * y = %d\n", mul(x, y)); printf("x / y = %d\n", mydiv(x, y)); return 0; }
-
让系统能找到我们写的动态库
这时用静态库的方式(gcc test.c -L./ -I./ -ltest)的方式是行不通的,运行./a.out,会报错。
原因:
- 当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。此时就需要系统动态载入器(dynamic linker/loader)。
- 对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段 — 环境变量LD_LIBRARY_PATH — /etc/ld.so.cache文件列表 — /lib/, /usr/lib目录找到库文件后将其载入内存。
还是要先执行gcc test.c -L./ -I./ -ltest
,然后按以下任意方式操作。
- 方式①:拷贝自己制作的共享库到/lib或者/usr/lib(不能是/lib64目录)。
- 方式②:临时设置LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
- 方式③:永久设置,把export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径,设置到~/.bashrc或者 /etc/profile文件中。
vim ~/.bashrc
#在最下面添加:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:库路径
source ~/.bashrc
- 方式④:将库路径添加到 /etc/ld.so.conf文件中
sudo vim /etc/ld.so.conf
#文件最后添加动态库路径(绝对路径)
sudo ldconfig -v #该命令会重建/etc/ld.so.cache文件
- 方式⑤:使用符号链接(使用绝对路径)
sudo ln -s 库路径/libtest.so /lib/libtest.so
4.GDB调试器
4.1 GDB简介
GNU工具集中的调试器是GDB(GNU Debugger),该程序是一个交互式工具,工作在字符模式。
除gdb外,linux下比较有名的调试器还有xxgdb, ddd, kgdb, ups。
GDB主要帮忙你完成下面四个方面的功能:
- 启动程序,可以按照你的自定义的要求随心所欲的运行程序。
- 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
- 当程序被停住时,可以检查此时你的程序中所发生的事。
- 动态的改变你程序的执行环境。
4.2 调试前的准备工作
-
准备一个测试用文件test.c
#include <stdio.h> void fun() { int i = 0; for (i = 0; i < 10; i++) { printf("fun==> i = %d\n", i); } } int main(int argc, char **argv) { //将传入参数全部输出 int i = 0; //c98标准不支持在for循环内定义变量 for (i = 0; i < argc; i++) { printf("argv[%d]: %s\n", i, argv[i]); } fun(); return 0; }
-
使用-g参数进行编译,生成调试信息
gcc -g test.c -o test #-g:把调试信息加到可执行文件中 #如果没有-g,将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。 #C++文件使用:g++ -g test.cpp -o test
-
启动GDB调试器
#启动GDB调试器 #语法:gdb <可执行文件名> gdb test #设置运行参数 #语法:set args <参数1> <参数2>... set args 10 20 30 40 50 #查看设置的运行参数 show args #启动程序 run #程序开始执行,如果有断点,停在第一个断点处 start #运行调试程序,直到主过程开始。
报错问题
在centos7上面gdb调试程序时候,报错:
Missing separate debuginfos, use: debuginfo-install glibc-2.17-157.el7_3.5.x86_64
解决方式:
- 先修改"/etc/yum.repos.d/CentOS-Debuginfo.repo"文件的 enable=1
- sudo yum install -y glibc
- debuginfo-install glibc
4.3 显示源代码
以下所有操作在启动GDB调试器后进行!
#用list命令来打印程序的源代码,默认打印10行(当前行的上5行和下5行)。
list n #打印第n行的上下文内容
list function #打印名为function的函数的源程序
list #显示当前行后面的源程序
list - #显示当前行前面的源程序
set listsize n #设置一次显示n行源代码
show listsize #查看当前listsize的设置
4.4 断点设置
以下所有操作在启动GDB调试器后进行!
4.4.1 简单断点
#break命令设置断点(可简写为b)
b 10 #在源程序第10行设置断点
b func #在func函数入口处设置断点
4.4.2 多文件设置断点
#C++中可使用class::function或function(type,type)格式来指定函数名。
#如果有命名空间可使用namespace::class::function或者function(type,type)格式来指定函数名。
break file:n #在源文件file的n行处设置断点
break file:function #在源文件file的function函数的入口处设置断点
break class::function #在类class的function函数的入口处设置断点
break function(type,type)
break namespace::class::function
4.4.3 条件断点
- 为断点设置一个条件,使用if关键词,后面跟其断点条件。
- 条件断点必须设置在循环体内。
break test.c:9 if i==5 #在test.c文件的第9行设置断点,当变量i等于5时触发断点
4.4.4 查询断点
#info可简写为i,break可简写为b
#以下四种都可以
info b
info break
i break
i b
4.4.5 维护断点
#delete可简写为d
delete 5 #删除断点号为5的断点
delete 5-7 #删除断点号5到7的所有断点
#如果不指定断点号,则表示删除所有的断点。
#disable可简写为dis
disable 5 #使5号断点失效
#如果不指定断点号,表示disable所有断点。
#enable可简写为ena
enable 5 #使5号断点生效
#如果不指定断点号,表示enable所有断点。
4.5 调试操作
以下所有操作在启动GDB调试器后进行!
run #运行程序,可简写为r
next #单步跟踪,函数调用时不进入函数,可简写为n
step #单步跟踪,函数调用会进入被调用函数体内,可简写为s
finish #退出进入的函数
until #在一个循环体内单步跟踪时,这个命令可以使程序运行到退出循环体,可简写为u。
continue #继续运行程序,停在下一个断点的位置,可简写为c
quit #退出gdb,可简写为q
4.6 查看修改变量的值
以下所有操作在启动GDB调试器后进行!
#print可简写为p
print i #打印变量i的值
ptype i #打印变量i的类型
print i=8 #修改变量i的值为8
set var width=66 #设置变量width的值为66
#p[/输出格式] <变量名>
#输出格式x(十六进制),d(十进制),u(十六进制无符号整型),o(八进制),t(二进制),c(字符),f(浮点数)
p/c i #将变量i以字符格式输出
4.7 自动显示变量的值
以下所有操作在启动GDB调试器后进行!
可以设置一些自动显示的变量,当程序停住时,或是在你单步跟踪时,这些变量会自动显示
display 变量名 #自动显示该变量的值
info display #查看display设置的自动显示的信息
#在info display的信息中可以看到:在我们设置自动显示的变量前都有一个编号
#可以用这个编号对自动显示的变量进行操作
undisplay 5 #取消编号为5的变量的自动显示
delete display 5 #删除编号为5的自动显示
disable display 5
enable display 5