linux系统编程(1)--GCC和GDB

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

步骤:

  1. 准备一些放进库中的测试文件。

    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;
    }
    

    类似可以完成减法,乘法,除法。

  2. 将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
    
  3. 使用打包工具ar将准备好的.o文件打包为.a文件。

    ar -rcs libtest.a add.o sub.o mul.o div.o
    #r:更新
    #c:创建
    #s:建立索引
    

3.2 静态库的使用

静态库制作完成之后,需要将.a文件和头文件一起发布给用户。

  1. 编写一个测试文件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;
    }
    
  2. 将拿到的.a文件和头文件放在和测试文件的同一目录下。

  3. 编译,然后就可以运行了

    gcc test.c -L./ -I./ -ltest -o test
    #-L:表示要连接的库所在目录
    #-I:表示指定头文件的目录为当前目录
    #-l(小写L):指定链接时需要的库,去掉前缀和后缀
    

3.3 动态库的制作

共享库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。

动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。

注意:

共享库的命名一般分为三个部分:

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

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

步骤:

  1. 准备一些放进库中的测试文件。

    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;
    }
    

    类似可以完成减法,乘法,除法。

  2. 生成目标文件。

    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),为了能够在多个应用程序间共享。
    
  3. 生成共享库

    gcc -shared add.o sub.o mul.o div.o -o libtest.so
    #-shared:指定生成动态链接库
    
  4. 通过nm命令查看对应的函数

    nm libtest.so | grep add
    nm libtest.so | grep sub
    

ldd命令:查看可执行文件的依赖的动态库

3.4 动态库的使用

  1. 编写测试文件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;
    }
    
  2. 让系统能找到我们写的动态库

    这时用静态库的方式(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主要帮忙你完成下面四个方面的功能:

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

4.2 调试前的准备工作

  1. 准备一个测试用文件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;
    }
    
  2. 使用-g参数进行编译,生成调试信息

    gcc -g test.c -o test		
    #-g:把调试信息加到可执行文件中
    #如果没有-g,将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。
    #C++文件使用:g++ -g test.cpp -o test
    
  3. 启动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

解决方式:

  1. 先修改"/etc/yum.repos.d/CentOS-Debuginfo.repo"文件的 enable=1
  2. sudo yum install -y glibc
  3. 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值