0%

gcc/gdb

7. GCC和gcc++编译器

7.1 gcc和 GCC是两个不同的东西
  • GCC:GNU Compiler Collection(GUN 编译器集合),它可以编译C、C++、JAV、Fortran、Pascal、Object-C、Ada等语言。
  • 因此gccGCC中的GUN C Compiler(C 编译器);g++GCC中的GUN C++ CompilerC++编译器)。二者都可以编译ccpp文件。只不过用gcc编译c++需要手动添加链接库
  • 在编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常使用用g++来编译以求自动完成链接。所以对于C语言程序的编译,我们应该使用gcc 指令,而编译C++程序则推荐使用g++指令
7.2 gcc/g++的四个步骤

gcc、g++编译器从拿到一个c源文件到生成一个可执行程序,中间一共经历了四个步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
第一步: 进行预处理
deng@itcast:~/share/3rd/1gcc$ gcc -E 1hello.c -o 1hello.i
第二步: 生成汇编文件
deng@itcast:~/share/3rd/1gcc$ gcc -S 1hello.i -o 1hello.s
第三步: 生成目标代码
deng@itcast:~/share/3rd/1gcc$ gcc -c 1hello.s -o 1hello.o
第四步: 生成可以执行文件
//这条指令是完成链接这个过程的,它通过链接器ld将运行程序的目标文件和库文件链接在一起,
//生成最后的可执行文件
deng@itcast:~/share/3rd/1gcc$ gcc 1hello.o -o 1hello
第五步: 执行
deng@itcast:~/share/3rd/1gcc​$ ./1hello
hello itcast

也可直接将源文件生成一个可以执行文件
deng@itcast:~/share/3rd/1gcc$ gcc 1hello.c -o 1hello
deng@itcast:~/share/3rd/1gcc​$ ./1hello
hello itcast

常用选项

1
2
3
4
5
6
7
8
9
10
gcc hello.c -o hello  -I /home/hello/include -L /home/hello/lib -lworld
上面这句表示在编译hello.c时:

-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录,
寻找的顺序是:/home/hello/include-->/usr/include-->/usr/local/include

-L /home/hello/lib表示将/home/hello/lib目录作为第一个寻找库文件的目录,
寻找的顺序是:/home/hello/lib-->/lib-->/usr/lib-->/usr/local/lib

-lworld表示在上面的lib的路径中寻找libworld.so动态库文件或libworld.a静态库文件

附加

  • -I针对头文件,未指明路径时,使用#include<>,gcc/g++默认目录/usr/include,如果使用#include<my.h>则找不到my.h文件,因此要通过-I dir参数来指定包含的头文件my.h的位置:

    1
    $gcc hell.c -o hell -I /root              (假设文件my.h存放在/root下)

  • -L-I 功能类似,只不过-L时对库文件使用,能够在指定库文件搜索路径。如果一个程序用到了目录/root/lib下的一个动态库libsunq.so,因为-L dir指定的是路径而没有指定文件,则需要用到 -llibname参数,它可以指定gcc去寻找libsunq.so或者libsunq.a

    1
    $gcc hello.c -o hell -L /root/lib -lsunq

7.3 静态链接和动态链接
7.3.1静态链接

静态链接是指由链接器在链接时将库的内容直接加入到可执行程序中。

  • 优点:
    • 对运行环境的依赖性较小,具有较好的兼容性
  • 缺点:
    • 生成的程序比较大,需要更多的系统资源,在装入内存时会消耗更多的时间
    • 库函数有了更新,必须重新编译应用程序
7.3.2 动态链接

动态链接是指连接器在链接时仅仅建立与所需库函数的之间的链接关系,在程序运行时才将所需资源调入可执行程序。

  • 优点:
    • 在需要的时候才会调入对应的资源函数
    • 简化程序的升级;有着较小的程序体积
    • 实现进程之间的资源共享(避免重复拷贝)
  • 缺点:
    • 依赖动态库,不能独立运行
    • 动态库依赖版本问题严重

注:前面我们编写的应用程序大量用到了标准库函数,系统默认采用动态链接的方式进行编译程序,若想采用静态编译,加入-static参数。

7.3.3 静态库的制作

静态库可以认为是一些目标代码的集合,是在可执行程序运行前就已经加入到执行码中,成为执行程序的一部分。 按照习惯,一般以.a做为文件后缀名。静态库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自己定义即可
  • 后缀:.a

所以最终的静态库的名字应该为:libxxx.a

1
2
3
4
5
6
7
步骤1:将c源文件生成对应的.o文件
deng@itcast:~/test/3static_lib$ gcc -c add.c -o add.o
deng@itcast:~/test/3static_lib​$ gcc -c sub.c -o sub.o
deng@itcast:~/test/3static_lib​$ gcc -c mul.c -o mul.o
deng@itcast:~/test/3static_lib​$ gcc -c div.c -o div.o
步骤2:使用打包工具ar将准备好的.o文件打包为.a文件 libtest.a
deng@itcast:~/test/3static_lib$ ar -rcs libAlogrithm.a add.o sub.o mul.o div.o
在使用ar工具是时候需要添加参数:-rcs

  • r更新
  • c创建
  • s建立索引
7.3.4 静态库的使用

静态库制作完成之后,需要将.a文件和头文件一起发布给用户。假设测试文件为main.c,静态库文件为libAlogrithm.a,头文件为Alogrithm.h

1
2
3
4
5
6
7
8
9
10
trluper@trluper-virtual-machine:~/Documents/test$ cat main.cpp 
#include <Alogrithm.h>
#include <iostream>
using namespace std;
int main(){
int a=20,b=20;
cout<<sum(a,b)<<endl;
return 0;
}
trluper@trluper-virtual-machine:~/Documents/test$ g++ -static main.cpp -I ../staticLib/ -L ../staticLib/ -lAlogrithm -o test

7.3.5 动图库的制作

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题和更新问题。 按照习惯,一般以“.so”做为文件后缀名。共享库的命名一般分为三个部分:

  • 前缀:lib
  • 库名称:自己定义即可
  • 后缀:.so

所以最终的动态库的名字应该为:libxxx.so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
步骤一:生成目标文件,此时要加编译选项:-fPIC(fpic)
deng@itcast:~/test/5share_lib$ gcc -fPIC -c add.c
deng@itcast:~/test/5share_lib​$ gcc -fPIC -c sub.c
deng@itcast:~/test/5share_lib$ gcc -fPIC -c mul.c
deng@itcast:~/test/5share_lib​$ gcc -fPIC -c div.c
参数:-fPIC 创建与地址无关的编译程序(pic,position independent code),是为了能够在多个应用程序间共享。
步骤二:生成共享库,此时要加链接器选项: -shared(指定生成动态链接库)
deng@itcast:~/test/5share_lib$ gcc -shared add.o sub.o mul.o div.o -o libtest.so

步骤三: 通过nm命令查看对应的函数
deng@itcast:~/test/5share_lib$ nm libtest.so | grep add
00000000000006b0 T add
deng@itcast:~/test/5share_lib​$ nm libtest.so | grep sub
00000000000006c4 T sub
7.3.6 使用动态库

静态库制作完后,需要在测试文件所在的目录创建libxxx.so的链接接,否则就会出现下面这个错误:

1
error while loading shared libraries: libtiger.so: cannot open shared object file: No such file or direct
可以通过ldd命令查看哪些动态链接库没有找到:
1
2
3
4
5
6
7
8
trluper@trluper-virtual-machine:~/Documents/test$ ldd test1
linux-vdso.so.1 (0x00007ffc0d5fe000)
libAlogrithm.so => not found
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fbd2317f000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbd22f57000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fbd22e70000)
/lib64/ld-linux-x86-64.so.2 (0x00007fbd233c1000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fbd22e50000)
这是因为程序运行时没有找到动态链接库造成的。程序编译时链接动态库和运行时使用动态链接库的概念是不同的,在运行时,程序链接的动态链接库需要在系统目录下才行。方法:

  • 方法一是可在系统目录创建软链接,链接文件。这是因为用·-L指定动态库文件路径只能保证编译通过,是否能执行还是得看/lib//usr/lib下面有没有该库文件(只要没有删除,永久有效)
  • 方法二当然你要可以修改LD_LIBRARY_PATH指定的动态库搜索路径(当前shell有效)
  • 方法三是配置文件/etc/ld.so.conf中指定的动态库搜索路径(永久有效)
1
deng@itcast:~/test/6share_test$ sudo ln -s /home/trluper/Documents/staticLib/libtest.so /lib/share_test/libtest.so

然后同静态链接一样,引用动态库去编译:(要保证能找到头文件,因此使用-I)

1
g++ -static main.cpp -I ../staticLib/ -lAlogrithm -o test

7.4 gdb调试

GDB是一个由GNU开源组织发布的、UNIX/LINUX操作系统下的、基于命令行的、功能强大的程序调试工具。 对于一名Linux下工作的c/c++程序员,gdb是必不可少的工具。GDB主要帮忙你完成下面四个方面的功能:

  • 启动程序,可以按照你的自定义的要求随心所欲的运行程序。
  • 可让被调试的程序在你所指定的调置的断点处停住。(断点可以是条件表达式)
  • 当程序被停住时,可以检查此时你的程序中所发生的事。
  • 动态的改变你程序的执行环境。
7.4.1 gdb的工作步骤

1. 准备工作

1
2
//使用gcc/g++将其编译为可执行文件,同时使用-g表示该程序可调试:
trluper@trluper-virtual-machine:~/Documents/staticLib$ g++ -g main.cpp sum.cpp -o main

2. 启动gdb

输入gdb -q + 可执行文件,启动gdb进行调试。-q参数可以屏蔽一些gdb版本等相关信息,使得页面看起来干净些(我用了)。至此gdb启动完毕:

1
2
3
trluper@trluper-virtual-machine:~/Documents/staticLib$ gdb -q main
Reading symbols from main...
(gdb)

3. 查看代码(可选)

输入list(l)即可查看程序源码

1
2
3
4
5
6
7
8
9
10
(gdb) l
1 #include "Alogrithm.h"
2 #include <iostream>
3 using namespace std;
4 int main()
5 {
6 int a=10,b=20;
7 cout<<sum(a,b)<<endl;
8 return 0;
9 }

4. 设置参数(可选)

set args可指定运行时参数(如:set args 10 20 30 40 50)。show args命令可以查看设置好的运行参数

1
2
3
4
5
6
7
(gdb) set args 20 30 50
(gdb) b 2
Breakpoint 1 at 0x11b5: file main.cpp, line 6.
(gdb) run
Starting program: /home/trluper/Documents/staticLib/main 20 30 50
gdb) show args
Argument list to give program being debugged when it is started is "20 30 50".

5.设置断点(可选)

输入break(b)+数字 可以对程序进行断点操作(数字就是设置断点的代码行数)

1
2
(gdb) b 2
Breakpoint 1 at 0x11b5: file main.cpp, line 6.

6. 启动程序

输入run(r)开始运行程序,直到遇到“断点”或者“结束”

1
2
3
4
5
6
7
(gdb) r
Starting program: /home/trluper/Documents/staticLib/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at main.cpp:6
6 int a=10,b=20;

7.单步执行程序(可选)

  • next单步执行程序,但是遇到函数时会直接跳过函数,不进入函数
  • step单步执行程序,但遇到函数会进入函数
  • continue继续执行程序,直到遇到断点或结束
1
2
3
4
5
6
7
8
9
10
11
12
Argument list to give program being debugged when it is started is "20 30 50".
(gdb) s
7 cout<<sum(a,b)<<endl;
(gdb) s
sum (a=10, b=20) at sum.cpp:4
4 return a+b;
(gdb) s
5 }
(gdb) s
30
main () at main.cpp:8
8 return 0;

8. 查看变量值(可选)

print + 变量查看变量值;whatis + 变量查看变量数据类型。

1
2
3
4
(gdb) print a
$1 = 10
(gdb) whatis a
type = int

9. 退出

输入q即可退出gdb

1
2
3
4
(gdb) q
A debugging session is active.
Inferior 1 [process 3102] will be killed.
Quit anyway? (y or n) y

7.4.2 其他命令

1. 断点

命令 功能
delete + n 删除第n个断点
disable + n 暂停第n个断点
enable + n 开启第n个断点
clear + n 清除第n行的断点
info b 显示当前程序的断点设置情况
delete breakpoints 清除所有断点

2. 运行信息

命令 功能
where/bt 当前运行的堆栈列表
bt backtrace 显示当前调用堆栈
up/down 改变堆栈显示的深度
info program 查看程序是否在运行,以及进程号被暂停的原因

3. 运行命令

命令 功能
until 如果你厌倦了在一个循环内单步跟踪,它可以运行程序直到退出循环体
until + 行号 运行至某行
finish 运行程序,直到当前函数返回完成,并且打印函数返回时的堆栈地址和返回值及参数值等信息
call + 行数 + (参数) 调用程序中可见的函数,并传递参数