文章一览
- 前言
- 一、gcc编译系统
-
- [1.1 文件名后缀](#1.1 文件名后缀)
- [1.2 C语言编译过程](#1.2 C语言编译过程)
- [1.3 gcc命令行选项](#1.3 gcc命令行选项)
- 二、gdb程序调试工具
-
- [2.1 启动gdb和查看内部命令](#2.1 启动gdb和查看内部命令)
- [2.2 显示源程序和数据](#2.2 显示源程序和数据)
-
- [2.2.1 显示和搜索源程序](#2.2.1 显示和搜索源程序)
- [2.2.2 查看运行时数据](#2.2.2 查看运行时数据)
- [2.3 改变和显示目录或路径](#2.3 改变和显示目录或路径)
- [2.4 控制程序的执行](#2.4 控制程序的执行)
-
- [2.4.1 设置断点](#2.4.1 设置断点)
- [2.4.2 显示断点](#2.4.2 显示断点)
- [2.4.3 删除断点](#2.4.3 删除断点)
- [2.4.4 运行程序](#2.4.4 运行程序)
- [2.4.5 程序的单步跟踪和连续执行](#2.4.5 程序的单步跟踪和连续执行)
- [2.4.6 函数调用](#2.4.6 函数调用)
- [2.5 其他常用命令](#2.5 其他常用命令)
前言
在数字世界的浩瀚星海中,Linux操作系统如同一颗璀璨的恒星,以其开源、稳定和强大的特性,照亮了无数开发者的编程之路。而C语言,作为最接近硬件层面的高级编程语言,以其无与伦比的性能和灵活性,成为了构建Linux系统的基石。在这个充满挑战与机遇的时代,掌握Linux环境下的C程序设计,不仅是技术追求,更是一种对极致性能和系统控制的渴望。
现在,让我们一起启程,深入Linux与C语言的神秘世界。
一、gcc编译系统
1.1 文件名后缀
目前Linux平台上最常用的C语言编译系统是gcc(GNU Compiler Collection)
常用文件名后缀及其表示的文件类型:
文件名后缀 | 文件类型 |
---|---|
.c | C源文件 |
.i | 预处理后的C源文件 |
.ii | 预处理后的C++源文件 |
.h | C或C++头文件 |
.C .cc .cp .cpp .c++ .cxx | C++源文件 |
.s | 汇编程序文件 |
.S | 必须预处理的汇编程序文件 |
.o | 目标文件 |
.a | 静态链接库 |
.so | 动态链接库 |
1.2 C语言编译过程
-
预处理阶段预处理程序 (Preprocessor)读取C语言源文件,对其中以"#"开头的指令(伪指令)和特殊符号进行处理。 伪指令主要包括文件包含、宏定义和条件编译指令。
-
编译阶段编译程序(Compiler)对预处理之后的输出文件进行词法分析和语法分析,试图找出所有不符合语法规则的部分。在确定各成分都符合语法规则后,将其"翻译"为功能等价的中间代码表示或者汇编代码。
-
汇编过程汇编程序(Assembler)把汇编语言代码翻译成目标机器代码的过程。
-
连接阶段 将一个文件中引用的符号(如变量或函数调用)与该符号在另外一个文件中的定义连接起来,从而使有关的目标文件连成一个整体,最终成为可被操作系统执行的可执行文件。连接模式分为静态连接和动态连接。
1.3 gcc命令行选项
$ gcc f1.c f2.c
(针对C语言源程序) 执行完成后,生成默认的可执行文件a.out。
按照选项作用所对应的编译阶段,可将gcc的选项分为四组:预处理选项 、编译选项 、优化选项 和连接选项。
- 预处理选项
选项格式 | 功能 |
---|---|
-C | 在预处理后的输出中保留源文件中的注释 |
-D name | 预定义一个宏name,而且其值为1 |
-D name=definition | 预定义一个宏name,并指定其值为definition所指定的值。其作用等价于在源文件中使用宏定义指令:#define name definition。但-D选项比宏定义指令的优先级高,它可以覆盖源文件中的定义 |
-U name | 取消先前对name的任何定义,不管是内置的,还是由-D选项提供的 |
-I dir | 指定搜索头文件的路径dir。先在指定的路径中搜索要包含的头文件,若找不到,则在标准路径(/usr/include, /usr/lib及当前工作目录)上搜索 |
-E | 只对指定的源文件进行预处理,不做编译,生成的结果送到标准输出 |
例:
bash
# 添加"-I /root"参数编译,/root为my.h文件位置
gcc 1-3.c -o 1-3 -I /root
- 编译程序选项
选项格式 | 功能 |
---|---|
-c | 只生成目标文件,不进行连接。用于对源文件的分别编译 |
-S | 只进行编译,不做汇编,生成汇编代码文件格式,其名与源文件相同,但扩展名为.s |
-o file | 将输出放在文件file中。如果未使用该选项,则可执行文件放在a.out中 |
-g | 指示编译程序在目标代码中加入供调试程序gdb使用的附加信息 |
-v | 在标准出错输出上显示编译阶段所执行的命令,即编译驱动程序及预处理程序的版本号 |
- 优化程序选项
优化分为对中间代码的优化和针对目标代码生成的优化。
- 连接程序选项
选项格式 | 功能 |
---|---|
object -file -name | 不以专用后缀结尾的文件名就认为是目标文件名或库名。连接程序可以根据文件内容来区分目标文件和库 |
-c -S -E | 如果使用其中任何一个选项,那么都不运行连接程序,而且目标文件名不应该用做参数 |
-llibrary | 连接时搜索由library命名的库。连接程序按照在命令行上给定的顺序搜索和处理库及目标文件。实际的库名是liblibrary.a |
-static | 在支持动态连接的系统中,它强制使用静态链接库,而阻止连接动态库;而在其他系统中不起作用 |
-Ldir | 把指定的目录dir加到连接程序搜索库文件的路径表中,即在搜索-l后面列举的库文件时,首先到dir下搜索,找不到再到标准位置下搜索 |
-Bprefix | 该选项规定在什么地方查找可执行文件、库文件、包含文件和编译程序本身数据文件 |
-o file | 指定连接程序最后生成的可执行文件名称为file,不是默认的a.out |
-
Linux下库文件的命名有一个约定,所有的库名都以lib开头 。因此,在-l选项所指定的文件名前自动地插入lib。并且约定,以.a(归档,archive)结尾的库是静态库,以**.so(共享目标,shared object)结尾的库是动态库**。
-
生成静态库的方法实际上可分为两步:
① 将各函数的源文件编译成目标文件。例如:
\$ gcc -c f1.c f2.c f3.c -o game.o
由此可得到各源文件的目标文件game.o。
② 使用ar工具将目标文件收集起来,放到一个归档文件中。例如:
\$ ar -rcs \$HOME/lib/libgame.a game.o
- 生成静态库以后,就可在编译C语言源文件时指明对它进行搜索、连接,例如:
\$ gcc f1.c f2.c f3.c -o mygame -static -L$HOME/lib -lgame
例:
gcc编译静态库
bash
#gcc 编译静态库并调用,演示如何将 add.c 打包成一个静态库来供 main.c 调用.
#1.首先将add.c编译成目标文件。
gcc -c add.c -o add.o
# 2.ar 打包静态库,将 add.o 打包成 libadd.a,
#参数 [-crv] :--c表示建立备存文件,- r表示将文件插入备存文件中,-v表示程序执行时显示详细的信息
ar -crv libadd.a add.o
#3.使用静态库 libadd.a编译main.c, [-L./] 表示将当前目录加到静态库的搜索路径
gcc main.c -L./ libadd.a -o main2
#4.执行
./main2
gcc 编译动态库
bash
#1.编译位置无关的目标文件 add.o,因为动态库动态加载到内存中的位置不确定,所以需要编译位置无关
gcc -fPIC -c add.c -o add.o
打包成动态库,
#2.如果不加 sudo 会提示没有权限,如果不指定/usr/lib/文件夹,会在执行时提示
#error while loading shared libraries:libadd.so: cannot open shared object file: No such file or directory
sudo gcc -shared add.o -o /usr/lib/libadd.so
#3.编译
sudo gcc -0 main3 main.c -L /usr/lib/ -ladd
#4.执行
./main3
二、gdb程序调试工具
程序中的错误按其性质可分为三种:
(1)编译错误,即语法错误。主要是程序代码中有不符合所用编程语言语法规则的错误。
(2)运行错误。如对负数开平方,除数为0,循环终止条件永远不能达到等 。
(3)逻辑错误 。这类错误往往是编程前对求解的问题理解不正确或算法不正确引起的,它们很难查找。查找程序中的错误,诊断其准确位置,并予以改正,这就是程序调试。程序调试分为人工查错 与机器调试。
gdb主要帮助用户在调试程序时完成四方面的工作:
(1)启动程序,可以按用户要求影响程序的运行行为。
(2)使运行程序在指定条件处停止。
(3)当程序停止时,检查它出现了什么问题。
(4)动态改变程序的执行环境,这样就可以纠正一个错误的影响,然后再纠正其他错误。
2.1 启动gdb和查看内部命令
为了发挥gdb的全部功能,需要在编译源程序时使用-g选项:
bash
gcc -g prog.c -o prog #(针对C语言源程序prog.c)
gcc -g program.cpp -o program # (针对C++源程序program.cpp)
启动gdb的常用方法有:
(1)以一个可执行程序作为gdb的参数:
$ gdb prgm
(2)同时以可执行程序和core文件作为gdb的参数:
$ gdb prgm core
启动gdb后就显示其提示符:(gdb),并等待用户输入相应的内部命令。
用户可以利用命令quit终止其执行,退出gdb环境。
2.2 显示源程序和数据
2.2.1 显示和搜索源程序
(1)显示源文件
利用list命令可以显示源文件中指定的函数 或代码行。
格式 | 功能 |
---|---|
list | 没有参数,显示当前行之后或周围的10多行 |
list -- | 显示先前10行之前的10行 |
list [file:] num | 显示源文件file中给定行号num周围的10行。如果缺少file,则默认为当前文件。例如,list 100 |
list start , end | 显示从行号start至end之间的代码行。例如,list 20,38 |
list [file:]function | 显示源文件file中指定函数function的代码行。如果缺少file,则默认为当前文件。例如,list meng1.c:square |
也可以利用set listsize命令重新设置一次显示源程序的行数:
set listsize linenum
(2)模式搜索
格式 | 功能 |
---|---|
forward-search regexp | 从列出的最后一行开始向前搜索给定的模式regexp(即正则表达式,一个字符串的匹配模式)。例如,forward-search i=* |
search regexp | 同上 |
reverse-search regexp | 从列出的最后一行开始向后搜索给定的模式regexp(即正则表达式,一个字符串的匹配模式)。例如,reverse-search i=?? |
2.2.2 查看运行时数据
(1)print命令
- 当被调试的程序停止时,可以用print命令(简写为p)或同义命令inspect来查看当前程序中运行的数据。
- print命令的一般使用格式:
print [/fmt] exp
print i (或p i)
显示当前变量i的值。
print i\*j (或p i\*j)
将根据程序当前运行的实际情况显示出i*j的值。
(2)gdb所支持的运算符
①用&运算符取出变量在内存中的地址,如:
print &i 显示变量i的存放地址。
print &array[i] 显示数组array第i个元素的地址。
② { type }adrexp 表示一个数据类型为type、存放地址为adrexp的数据。
③ @ 是一个与数组有关的双目运算符,使用形式如:
print array@10
打印从array(数组名,即数组的基地址)开始的10个值。
print array[3]@5
打印从array第三个元素开始的5个数组元素的数值。
④file :: var
(或者 function :: var ) 表示文件file(或者函数function)中变量var的值。例如:
print inner::i 打印函数inner中变量i的当前值。
(3)输出格式
由表示格式的字母(如o、x、d、u、t、f、a、i、c、s )和表示数据长度的字母(如b、w、h、g)组成。
2.3 改变和显示目录或路径
(1)directory命令
将给定目录dir添加到源文件搜索路径的开头,并且忽略先前保存的有关源文件和代码行位置的信息。其一般格式是:
directory [dir] 或者 dir [dir]
(2)cd命令
cd命令将调试程序和被调试程序的工作目录置为指定的目录dir。其使用格式为:
cd dir
(3)path命令
利用path命令可以将一个或多个目录添加到目标文件搜索路径的开头。其使用格式是:
path dirs
(4)pwd命令
该命令用来显示工作目录。
(5)show directories命令
该命令显示定义的源文件搜索路径。
(6)show paths命令
该命令显示当前查找目标文件的搜索路径。
2.4 控制程序的执行
2.4.1 设置断点
编译源程序时需要使用-g选项
在gdb中用break命令(其缩写形式为b)设置断点:
● break linenum
(在当前文件指定行linenum处设置断点,停在该行开头)
● break linenum if condition
(在当前文件指定行linenum处设置断点,但仅在条件表达式condition成立时才停止程序执行)
● break function
(在当前文件函数function的入口处设置断点)
● break file:linenum
(在源文件file的linenum行上设置断点)
● break file:function
(在源文件file的函数function的入口处设置断点)
● break *address
(运行程序在指定的内存地址address处停止)
● break
(不带任何参数,则表示在下一条指令处停止)
断点应设置在可执行的行上,不应是变量定义之类的语句。
2.4.2 显示断点
bash
info breakpoints [num]
info break [num]
2.4.3 删除断点
delete [bkptnums]
2.4.4 运行程序
run [args] (run简写是r)
2.4.5 程序的单步跟踪和连续执行
(1)单步跟踪
step [N] 参数N表示每步执行的语句行数。 进入被调用函数内部执行。
next [N] 参数N表示每步执行的语句行数。 被调用函数被当做一条指令执行。
stepi(缩写为si)或nexti(缩写为ni)命令一条一条地执行机器指令。
(2)连续执行
利用continue,c或fg命令连续执行到下一个断点
2.4.6 函数调用
call expr
其中,expr是所用编程语言的函数调用表达式,包括函数名 和实参。
- 在调试过程中,可以使用return命令强行从正在执行的函数中退出:
return [expr]
- 还可以使用finish命令退出函数,但它并不立即退出,而是继续运行,直至当前函数返回
2.5 其他常用命令
1.执行shell命令
shell command-string
例如:
(gdb) shell date
2009年 03月 31日 星期二 16:47:56 CST
(gdb)
2.修改变量值
(gdb) print x=10
(gdb) set variable x=10
3.跳转执行
jump linenum (参数linenum表示下一条语句的行号。)
jump *addr (参数 addr表示下一条代码行的内存地址。)