Linux 系统编程(一):C/C++程序编译与gdb调试

程序编译方法和 gdb 调试技巧,严格来说不应该算作 Linux 系统编程的内容,但 Linux 系统编程主要使用 C 语言进行,离不开程序编译、调试的基础,所以将这部分知识作为第一部分的入门知识。

在此仅仅对我所学到的做一个粗略简单的总结。

1 编译基本过程

C/C++ 程序编译主要有四个步骤

  • 预处理:由预处理器完成,生成.i文件;主要做些代码文本替换工作,如展开头文件、展开宏、处理条件编译过滤不必要代码,过滤注释,添加行号和文件名标识,保留#pragma指令,该指令由编译器处理;
  • 编译:由编译器完成,生成.s文件;进行语法、词法分析,检查是否有错,并且根据编译器的不同还会对代码进行不同的优化,在 gcc 中优化的级别可以设定,最后翻译成汇编语言;
  • 汇编:由汇编器完成,生成.o文件;将汇编语言翻译成目标机器指令,目标文件由段组成,至少包含代码段和数据段;
  • 链接:由链接器完成,一般生成.out格式,在 Linux 中无所谓;将目标文件与其他使用到的函数的源文件、库进行链接,生成二进制可执行文件;

2 gcc/g++ 编译指令

gcc 和 g++都能编译 C/C++代码,仅仅稍有区别

  • 对于文件后缀为.c的,gcc 把它当成是C 程序,而 g++当作是C++程序;后缀为.cpp 的,两者都会认为是C++程序。(两者语法不完全一样)
  • 编译阶段,g++会调用 gcc,对于 C++代码,两者是等价的,但是因为 gcc 命令不能自动和 C ++程序使用的库联接,所以通常用 g++来完成链接
  • 对于__cplusplus 宏,实际上,这个宏只是标志着编译器将会把代码按 C 还是 C++语法来解释,如上所述,如果后缀为.c,并且采用 GCC 编译器,则该宏就是未定义的,否则,就是已定义

上述规则看似有点绕,反正我的习惯就是 C 用gcc,C++用g++,肯定不会错。

1
2
3
4
5
6
7
8
9
10
11
12
13
# 预处理指令,-o选项用于命名
gcc -E file.c -o file.i
# 编译指令
gcc -S file.i -o file.s
# 汇编指令
gcc -c file.s -o file.o
gcc -c file1.c -o file1.o
# 链接指令
gcc -o file.o file1.o -o file.out
# 显示所有警告信息
gcc -o file.o file1.o -o file.out -Wall
# 向程序中注册宏定义
gcc -o file.o file1.o -o file.out -D DEBUG

3 静态库制作和使用

所谓静态和动态,是指链接过程是静态的还是动态的。

程序在与静态库链接时, 会将汇编生成的目标文件.o 与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。

故而静态库类似于汇编生成的.o目标文件,其特点如下

  • 静态库对函数库的链接是放在编译时期完成的。
  • 程序在运行时与函数库再无瓜葛,移植方便。
  • 浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
  • 静态库更新,程序需要重新编译进行全量更新

3.1 静态库制作

静态库命名和格式有固定规则,命名必须以lib开头,格式一般为.a.lib

以加减乘除为例,先写一个.c文件

mymath.c
1
2
3
4
5
6
7
8
9
10
11
12
int add(int a, int b) {
return a + b;
}
int sub(int a, int b) {
return a - b;
}
int mul(int a, int b) {
return a * b;
}
int div(int a, int b) {
return a / b;
}

再写一个头文件

mymath.h
1
2
3
4
5
6
7
8
9
#if !defined(MYMATH_H_)
#define MYMATH_H_

int add(int, int);
int sub(int, int);
int mul(int, int);
int div(int, int);

#endif // MYMATH_H_

然后开始制作静态库

1
2
3
4
# 首先生成源代码的目标文件
gcc -c mymath.c -o mymath.o
# 使用ar工具制作静态库
ar rcs libmymath.lib mymath.o

至此文件目录下就多出libmymath.lib静态库文件了。

3.2 使用静态库

写一个 C 文件

testlib.c
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
#include "mymath.h"

int main() {
int a = 6, b =2;
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", sub(a, b));
printf("a * b = %d\n", mul(a, b));
printf("a / b = %d\n", div(a, b));
return 0;
}

然后将此文件与静态库一起编译、链接

1
gcc testlib.c libmymath.lib -o testlib.out

然后运行生成的可执行文件

1
2
3
4
5
$ ./testlib.out
a + b = 8
a - b = 4
a * b = 12
a / b = 3

4 动态库制作和使用

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新

动态库又称为分享库,其特点如下

  • 动态库把对一些库函数的链接载入推迟到程序运行的时期。

  • 可以实现进程之间的资源共享。(因此动态库也称为共享库)

  • 将一些程序升级变得简单。

  • 甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。

不同平台下制作动态库有些差异

  • 在 Windows 系统下的执行文件格式是 PE 格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。

  • Linux 下 gcc 编译的执行文件默认是 ELF 格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。

4.1 动态库的制作

动态库的制作比静态库简单,无需使用ar进行打包。

同样以加减乘除的 C 代码为例

1
2
3
4
5
# 使用两条指令
gcc -fPIC -c mymath.c
gcc -shared -o libDynMymath.so mymath.o
# 或者合并为一条指令
gcc -fPIC -shared -o libDynMymath.so mymath.c

4.2 动态库的使用

由于刚才生成的动态库在当前目录下,不在系统默认的动态库路径中,所以使用前需要先将动态库路径的环境变量声明为当前目录,或者将制作的动态库移动到默认的动态库路径。

1
2
3
4
5
6
7
8
9
10
# 声明环境变量
export LD_LIBRARY_PATH=~/cppunp/hmunp
# 与动态库链接,注意-L参数后带动态库路径
gcc testlib.c -o testdynlib.out -lDynMymath -L../hmunp/
# 执行生成程序
./testdynlib.out
a + b = 8
a - b = 4
a * b = 12
a / b = 3

5 makefile 的编写

在一个 C/C++ 项目中,通常一个程序需要多个文件来进行编译完成,使用gcc指令来生成所有目标文件、再链接生成程序,未免过于繁琐,而且在对项目文件进行修改后,每次使用gcc指令重新编译也非常麻烦、耗时。

makefile文件可以大大简化这一过程,几乎一劳永逸,书写完成后即使项目结构发生很大变化,也仅需要做很小的更改,甚至无需更改。

另外每次使用makefile来对项目进行编译时,其仅会对更改过的文件重新生成目标文件,大大降低了程序总编译时间。

5.1 所使用的示例代码

这里同样以加减乘除的程序作为示例,但是将加减乘除分为四个 c 文件。如下

add.c
1
2
3
int add(int a, int b) {
return a + b;
}
sub.c
1
2
3
int sub(int a, int b) {
return a - b;
}
mul.c
1
2
3
int mul(int a, int b) {
return a * b;
}
div.c
1
2
3
int div(int a, int b) {
return a / b;
}

然后将四个函数声明放入一个头文件

mymath.h
1
2
3
4
5
6
7
8
9
#if !defined(MYMATH_H_)
#define MYMATH_H_

int add(int, int);
int sub(int, int);
int mul(int, int);
int div(int, int);

#endif // MYMATH_H_

再写一个主程序测试函数

main.c
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include "mymath.h"

int main() {
int a = 10, b = 2;
printf("a + b = %d\n", add(a, b));
printf("a - b = %d\n", sub(a, b));
printf("a * b = %d\n", mul(a, b));
printf("a / b = %d\n", div(a, b));
}

如果直接使用gcc命令来编译链接生成程序的话,我们需要以下指令

1
2
gcc -c add.c sub.c mul.c div.c
gcc main.c add.o sub.o mul.o div.o -o main.out

然后测试一下生成的主程序

1
2
3
4
5
./main.out
a + b = 12
a - b = 8
a * b = 20
a / b = 5

5.2 makefile 的基本规则

makefile类似于shell脚本,可以使用shell函数、语法以及通配符。

makefile的规则包含两个部分,一个是依赖关系,一个是生成目标的方法即对应指令。

makefile中,规则的顺序是很重要的,因为,makefile中只应该有一个最终目标,其它的目标都是被这 个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在makefile中的目标 可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么 ,第一个目标会成为最终的目标。make 所完成的也就是这个目标。

基本格式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ALL 指定最终生成的程序
ALL:hello.out

# 冒号左边是目标文件,右边是依赖文件
# 下一行一个Tab后书写对应命令
hello.out:hello.o
gcc hello.o -o hello.out

hello.o:hello.c
gcc -c hello.c -o hello.o

# make clean 指令将会执行的指令
# 第一个'-'表示删除不存在的文件不报错
clean:
-rm -rf *.o *.out

根据基本格式,我们可以用示例代码写一个最基础的makefile文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ALL:main

main:main.o add.o sub.o mul.o div.o
gcc main.o add.o sub.o mul.o div.o -o main

mian.o:mian.c
gcc -c main.c -o main.o

add.o:add.c
gcc -c add.c -o add.o

sub.o:sub.c
gcc -c sub.c -o sub.o

mul.o:mul.c
gcc -c mul.c -o mul.o

div.o:div.c
gcc -c div.c -o div.o

clean:
-rm -rf *.o *.out main

由于之前已经用gcc命令编译过一次,所以先执行make clean将之前生成的目标文件和主程序删除,再执行make使用makefile文件规则进行编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ make clean
rm -rf *.o *.out main
$ make
cc -c -o main.o main.c
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
gcc main.o add.o sub.o mul.o div.o -o main
$ ./main
a + b = 12
a - b = 8
a * b = 20
a / b = 5

可见这个最基础的makefile文件已经奏效。

5.3 加入 shell 函数和自动变量

显然,如果一个项目中文件很多的话,上面的makefile文件将会变得极其冗长,光依赖文件就要写很长一串,这显然很不方便。

可以引入两个shell函数

1
2
3
4
5
# 将./目录下所有.c文件的文件名组成列表,然后赋值给src变量
src = $(wildcard ./*.c)

# 将参数3的src中所有符合参数1匹配规则的文件替换为参数2的格式,得到的新列表复制给obj变量
obj = $(patsubst %c, %.o, $(src))

再介绍 3 个自动变量

  • $@:用于表示目标
  • $^:用于表示所有依赖条件
  • $<:用于表示第一个依赖条件,还可以将依赖条件从依赖列表中依次取出

makefile中的应用如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))

ALL:main

main:$(obj)
gcc $^ -o $@
# main.o 不生成也无妨,链接时会自动生成
mian.o:mian.c
gcc -c $< -o $@

add.o:add.c
gcc -c $< -o $@

sub.o:sub.c
gcc -c $< -o $@

mul.o:mul.c
gcc -c $< -o $@

div.o:div.c
gcc -c $< -o $@

clean:
-rm -rf $(obj) main

这个版本在命名上取得了一定简化,但依然有大量类似代码来生成各种目标文件,实际上这部分类似代码可以使用%.o:%.c的规则来简化。优化如下

1
2
3
4
5
6
7
8
9
10
11
12
13
src = $(wildcard ./*.c)
obj = $(patsubst %.c, %.o, $(src))

ALL:main

main:$(obj)
gcc $^ -o $@

%.o:%.c
gcc -c $< -o $@

clean:
-rm -rf $(obj) main

这样就简单很多了,依然在 shell 中测试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ make clean
rm -rf ./mul.o ./main.o ./add.o ./div.o ./sub.o main
$ make
gcc -c mul.c -o mul.o
gcc -c main.c -o main.o
gcc -c add.c -o add.o
gcc -c div.c -o div.o
gcc -c sub.c -o sub.o
gcc mul.o main.o add.o div.o sub.o -o main
$ ./main
a + b = 12
a - b = 8
a * b = 20
a / b = 5

5.4 源代码在子文件夹时的优化

之前所有的源文件、头文件以及makefile文件都在同一个文件夹里,在实际中更常见的情况应该是源文件、头文件都放在不同的文件夹中,而makefile文件则在根目录下。并且有时也需要加入或更改一些编译的参数如-Wall和用于调试的-g等。这里根据该种情况对makefile文件进行优化。

假如所有.c 文件都放入src的子文件夹,所有头文件都放入inc的子文件夹,然后新建一个obj文件用以存放生成的目标文件。结构如下

1
2
3
4
5
6
7
8
9
10
11
12
$ tree
.
├── inc
│ └── mymath.h
├── makefile
├── obj
└── src
├── add.c
├── div.c
├── main.c
├── mul.c
└── sub.c

源文件的路径变化,只需在makefile文件中修改wildcardpatsubst函数的参数即可;由于头文件也更改了路径,在主程序中不做修改的话,则需在生成目标文件时指定头文件目录,所以需要一个变量来保存头文件路径;关于编译参数-Wall-g,为了方便修改,也将其定义为一个变量。

优化后如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
src = $(wildcard ./src/*.c)
obj = $(patsubst ./src%.c, ./obj/%.o, $(src))

inc_path = ./inc
myArgs = -Wall -g

ALL:main

main:$(obj)
gcc $^ -o $@ $(myArgs)

$(obj):./obj/%.o:./src/%.c
gcc -c $< -o $@ $(myArgs) -I $(inc_path)

clean:
-rm -rf $(obj) main
# 伪目标,防止有生成名为clean的文件后,make clean指令失效
.PHONY:clean

注意文件中关于伪目标的注释。

还有一种情况就是同一个项目有时需要使用不同的makefile规则来进行测试或适应不同的环境,那么可以在使用make指令时加上-f选项,后面再接指定的makefile文件名。如新建一个makefile文件,将其命名为make_ubuntu

1
make -f make_ubuntu

6 GDB 调试基础

GDB, 是 The GNU Project Debugger 的缩写, 是 Linux 下功能全面的调试工具。GDB 支持断点、单步执行、打印变量、观察变量、查看寄存器、查看堆栈等调试手段。在 Linux 环境软件开发中,GDB 是主要的调试工具,用来调试 C 和 C++ 程序。

实际上,一般调试程序都会借助一些 IDEA 或者编辑器上的插件,比如我之前都是用 VScode 进行配置后来调试的。

虽然直接使用 GDB 的情况并不多,但了解一些基本指令和方法还是有一些帮助的。在此对 GDB 的一些调试指令做一个简单的总结记录。

6.1 GDB 使用方法

在使用 GDB 调试前,要保证生成的程序包含调试信息,也就是说在使用 gcc 进行编译生成程序时,必须要加-g参数来生成调试信息。

这里使用一个简单的示例程序

testgdb.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
// 使用随机数初始化数组
void init_arr(int* arr, int len) {
for(int i = 0; i < len; ++i)
arr[i] = rand() % 20 + 1;
}
// 将数组进行打印输出
void print_arr(int *arr, int len) {
printf("arr: ");
for (int i = 0; i < len; ++i)
printf("%d ", arr[i]);
printf("\n");
}
// 将数组进行选择排序
void select_sort(int *arr, int len) {
int i, j, k, tmp;
for (i = 0; i < len - 1; ++i) {
k = i;
for (j = i + 1; j < len; ++j) {
if (arr[k] > arr[j])
k = j;
}
if ( i != k) {
tmp = arr[i];
arr[i] = arr[k];
arr[k] = tmp;
}
}
}

int main() {
int arr[20];
char *p = "hello";

srand(time(NULL));
init_arr(arr, 20);
print_arr(arr, 20);
select_sort(arr, 20);
printf("------------after sort-----------\n");
print_arr(arr, 20);
return 0;
}

使用 gcc 来编译程序

1
gcc testgdb.c -o test -g

然后进入 gdb 命令界面

1
2
3
4
5
6
gdb test
GNU gdb (Ubuntu 8.1-0ubuntu3.2) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later
...省略n行
(gdb)

在 gdb 命令行界面,输入 run 执行待调试程序

1
(gdb) run

在 gdb 命令行界面,输入 quit 退出 gdb

1
(gdb) quit

在 gdb 命令行界面,使用 (gdb) help command 可以查看命令的用法

在 gdb 命令行界面可以执行外部的 Shell 命令

1
(gdb) !shell 命令

例如查看当前目录的文件:

1
2
(gdb) !ls
test testgdb.c

6.2 GDB 常用指令

list/l 查看源代码

使用list 1从第一行列出源代码,数字可以更改

1
2
3
4
5
6
7
8
9
10
11
(gdb) list 1
1 #include <time.h>
2 #include <stdlib.h>
3 #include <stdio.h>
4
5 void init_arr(int* arr, int len) {
6 for(int i = 0; i < len; ++i)
7 arr[i] = rand() % 20 + 1;
8 }
9
10 void print_arr(int *arr, int len) {

继续输入listl可以继续显示剩下的代码

1
2
3
4
5
6
7
8
9
10
11
(gdb) l
11 printf("arr: ");
12 for (int i = 0; i < len; ++i)
13 printf("%d ", arr[i]);
14 printf("\n");
15 }
16
17 void select_sort(int *arr, int len) {
18 int i, j, k, tmp;
19 for (i = 0; i < len - 1; ++i) {
20 k = i;

break/b 设置断点

使用breakb设置断点

1
2
3
4
(gdb) break 38
Breakpoint 1 at 0xa07: file testgdb.c, line 38.
(gdb) b 40
Breakpoint 2 at 0xa29: file testgdb.c, line 40.

所设置的断点可以使用info b来查看。

1
2
3
4
(gdb) info b
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000008000a43 in main at testgdb.c:38
2 breakpoint keep y 0x0000000008000a65 in main at testgdb.c:40

还可以设置条件断点

1
2
(gdb) b 20 if i = 5
Breakpoint 3 at 0x8000904: file testgdb.c, line 20.

run/r 运行程序

1
2
3
4
5
(gdb) run
Starting program: /home/choupin/cppunp/hmapue/day02/test

Breakpoint 1, main () at testgdb.c:38
38 init_arr(arr, 20);

next/n 运行至下一语句

1
2
3
4
5
6
7
(gdb) next
39 print_arr(arr, 20);
(gdb) next
arr: 14 8 11 3 16 15 12 16 13 20 8 20 4 11 10 2 8 2 13 4

Breakpoint 2, main () at testgdb.c:40
40 select_sort(arr, 20);

step/s 单步调试

单步调试遇到函数调用,将会进入函数,逐句执行

1
2
3
4
5
6
7
8
9
10
11
12
13
(gdb) n
arr: 16 17 19 17 17 9 15 1 6 12 4 8 16 19 2 12 20 7 20 15

Breakpoint 2, main () at testgdb.c:40
40 select_sort(arr, 20);
# 接上一步
(gdb) s
select_sort (arr=0x7ffffffedc50, len=20) at testgdb.c:19
19 for (i = 0; i < len - 1; ++i) {
(gdb) s
20 k = i;
(gdb) step
21 for (j = i + 1; j < len; ++j) {
1
2
3
4
5
6
7
8
(gdb) p i
$1 = 0
(gdb) p j
$2 = 32767
(gdb) s
22 if (arr[k] > arr[j])
(gdb) p j
$3 = 1

finish 结束当前函数调用

1
2
3
4
5
6
7
8
(gdb) finish
Run till exit from #0 select_sort (arr=0x7ffffffedc50, len=20)
at testgdb.c:21
main () at testgdb.c:41
41 printf("------------after sort-----------\n");
(gdb) p arr
$5 = {1, 2, 4, 6, 7, 8, 9, 12, 12, 15, 15, 16, 16, 17, 17, 17,
19, 19, 20, 20}

continue 继续执行断点后续语句

1
2
3
4
5
(gdb) continue
Continuing.
------------after sort-----------
arr: 1 2 4 6 7 8 9 12 12 15 15 16 16 17 17 17 19 19 20 20
[Inferior 1 (process 5646) exited normally]

6.3 GDB 其他指令

设置 main 函数命令行参数

进行 gdb 命令行后,可以有两种方法设置main函数的命令行参数。

先修改一下源文件

1
2
3
4
5
6
7
8
9
10
11
12
13
-int main() {
+int main(int argc, char *argv[]) {
+ printf("%s %s %s\n", argv[1], argv[2], argv[3]);
int arr[20];
char *p = "hello";
srand(time(NULL));
init_arr(arr, 20);
print_arr(arr, 20);
select_sort(arr, 20);
printf("------------after sort-----------\n");
print_arr(arr, 20);
return 0;
}

重新编译生成程序,再进入 gdb。

第一种,在run之前使用set args设置参数,再runstart

1
2
3
4
5
6
7
8
(gdb) set args aa bb cc
(gdb) run
Starting program: /home/choupin/cppunp/hmapue/day02/test aa bb cc
aa bb cc
arr: 18 17 9 13 1 16 8 7 18 5 3 11 2 15 16 16 16 1 10 12
------------after sort-----------
arr: 1 1 2 3 5 7 8 9 10 11 12 13 15 16 16 16 16 17 18 18
[Inferior 1 (process 5818) exited normally]

第二种,在run的时候直接加所需要的参数

1
2
3
4
5
6
7
(gdb) run aa bb cc
Starting program: /home/choupin/cppunp/hmapue/day02/test aa bb cc
aa bb cc
arr: 15 4 19 7 17 19 6 2 12 18 6 6 9 15 20 9 12 15 5 1
------------after sort-----------
arr: 1 2 4 5 6 6 6 7 9 9 12 12 15 15 15 17 18 19 19 20
[Inferior 1 (process 5822) exited normally]

ptype 查看变量类型

1
2
(gdb) ptype arr
type = int [20]

backtrace/bt 列出当前程序正在存活的栈帧

nextselect_sort()函数,然后step进入,再使用bt查看当前栈帧

1
2
3
4
5
6
7
8
9
10
11
(gdb) n
arr: 5 12 1 18 4 7 18 13 1 9 3 16 7 16 1 20 4 8 8 15

Breakpoint 2, main (argc=4, argv=0x7ffffffedd78) at testgdb.c:40
40 select_sort(arr, 20);
(gdb) s
select_sort (arr=0x7ffffffedc30, len=20) at testgdb.c:19
19 for (i = 0; i < len - 1; ++i) {
(gdb) bt
#0 select_sort (arr=0x7ffffffedc30, len=20) at testgdb.c:19
#1 0x0000000008000a76 in main (argc=4, argv=0x7ffffffedd78) at testgdb.c:40

frame 切换栈帧

切换栈帧需要知道当前栈帧的编号

1
2
3
4
5
6
(gdb) frame 0
#0 select_sort (arr=0x7ffffffedc30, len=20) at testgdb.c:19
19 for (i = 0; i < len - 1; ++i) {
(gdb) frame 1
#1 0x0000000008000a76 in main (argc=4, argv=0x7ffffffedd78) at testgdb.c:40
40 select_sort(arr, 20);

display/undisplay 设置或取消跟踪变量

使用display可以设置需要跟踪的变量,之后再使用run,将会在每一步展示设置后的变量,变量前是展示的编号,可以使用display加编号来取消。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(gdb) display arr
3: arr = (int *) 0x7ffffffedc30
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/choupin/cppunp/hmapue/day02/test aa bb cc
aa bb cc

Breakpoint 1, main (argc=4, argv=0x7ffffffedd78) at testgdb.c:38
38 init_arr(arr, 20);
1: arr = {9, 0, -12573088, 32767, -74584, 32767, 15775231, 0, 1, 0, 134220541, 0,
-12514912, 32767, 0, 0, 134220464, 0, 134219536, 0}
(gdb) n
39 print_arr(arr, 20);
1: arr = {19, 6, 14, 16, 17, 17, 13, 9, 16, 17, 3, 11, 3, 18, 10, 10, 4, 2, 1, 16}
(gdb) undisplay 1

7 结束语

本篇文章从介绍 C/C++ 程序最基本的编译链接过程开始,接着介绍了 Linux 平台下使用gcc编译程序的基本指令,以此为基础,介绍了静态库和动态库,以及相应的制作和使用方法,最后两个部分分别记录了 makefile文件的书写方法和GDB调试的常用指令。虽然本篇文章的内容并不和 Linux 系统编程直接相关,但确实是重要的基础。之后的部分将会记录 Linux 系统编程常用的函数,以及一些系统指令的实现。

-------- 本文结束 感谢阅读 --------
给我加块红烧肉吧
  • 本文标题: Linux 系统编程(一):C/C++程序编译与gdb调试
  • 本文作者: Chou Bin
  • 创建时间: 2020年02月15日 - 20时02分
  • 修改时间: 2020年02月15日 - 21时02分
  • 本文链接: http://yoursite.com/2020/02/15/apue-hm01/
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!