《gcc编译器及gdb调试器入门》学习笔记

前言

在B站进行学习的,地址见学习视频地址

gcc编译器简介

gcc的文件类型约定规则

文件后缀名文件类型约定
.cC语言源代码文件
.a由目标文件构成的档案库文件
.C,.cc或cxxC++源代码文件
.h程序所包含的头文件
.i经预处理过的C源代码文件
.ii经预处理过的C++源代码文件
.mObjective-C源代码文件
.o编译后的目标文件
.s汇编语言源代码文件
.S经过预编译的汇编语言源代码文件

gcc常用选项

文件后缀名文件类型约定
.o filename (常用)指定输出文件名,在编译为目标代码时,这一选项不是必须的。如果filename没有指定,缺省文件名是a.out
.c只编译不链接,生成目标文件".o"
-S只编译不汇编,生成汇编代码
-E只进行预编译,不做其他处理
-g (常用)在生成的可执行程序中包含标淮调试信息
-v打印编译器内部编译各过程的命令行信息和编译器的版本号
-I dir (常用)在头文件的搜索路径列表中添加dir目录
-L dir (常用)在库文件的搜索路径列表中添加dir目录
-static (常用)链接静态库
-library链接名为library的库文件
-Dmacro定义指定的宏,使它能够通过源码中的#ifdef进行检验
-O、-O2、-O3 (常用)将优化状态打开,该选项不能与-g选项联合使用
-Wall在发生警告时取消编译操作,即将警告看作是错误
-Werror在发生警告时取消编译操作,即将警告看作是错误
-w禁止所有的报警
-pedantic (常用)严格要求符合ANSI标淮

如何使用gcc

gcc编译器的功能强大,包括警告提示功能、代码优化、连接库、使用管道加速等。

gcc编译初步

gcc的编译过程分为预处理、编译、汇编、链接四个阶段,从功能上分,预处理、编译和汇编是三个不同的阶段,但gcc在实际操作上,可以把这个步骤合并为一个步骤来执行。

//hello.c
#include <stdio.h>
int main(void)
{
	printf("hello world,Linux programming!\n");
	return 0;
}

命令:

gcc -o hello hello.c

gcc编译初步

  • 为了更好地理解gcc的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每一步的运行结果。
  • 第一步进行的是预处理。在预处理阶段,gcc把预处理命令扫描处理完毕,输入C语言的源文件,通常为*.c,它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、#include和#define等预处理命令。该阶段会生成一个中间文件*.i,但实际工作中通常不会专门生成这种文件,基本上用不到,若非要生成这种文件,可以使用-E参数让gcc在预处理结束后停止编译过程:
gcc -E hello.c -o hello.i
  • 此时若查看hello.i文件中的内容,会发现头文件stdio.h的内容确实都插入到文件里去了,而其它应当被预处理的宏定义也都做了相应的处理。
  • 第二步是编译阶段。在编译阶段,gcc把预处理后的结果编译成汇编或者目标模块。输入的是中间文件*.i,编译后生成汇编语言文件*.s。这个阶段对应的gcc命令如下所示:
gcc -S hello.i -o hello.s
  • 下一步进行的是汇编。在汇编阶段,编译器把编译出
    来的结果汇编成具体CPU上的目标代码模块。输入汇编文件*.s,输出机器语言*.o。这个阶段可以通过使用-c参数来完成:
gcc -c hello.s -o hello.o

最后,在链接阶段把多个目标代码模块连接生成一个
大的目标模块。输入机器代码文件*.o (与其他的机器代码文件和库文件),汇集成一个可执行的二进制代码文件。这一步骤可以利用下面的示例命令完成:

gcc hello.o -o hello

到这里gcc就完成了整个编译过程,得到可执行文件。

警告提示功能

让gcc产生告警的代码:

//warning_code.c
#include<stdio.h>
void main(void)
{
	long long int var = 2010;
	printf("It is a warning code for gcc! \n");
}

下面来看看gcc是如何帮助程序员来发现这些错误的。当gcc在编译不符合ANSI/ISO C语言标准的源代码时,如果加上了-pedantic选项,那么在使用了扩展语法的地方将产生相应的警告信息,如下:

gcc -pedantic warning_code.c -o warning_code
  • 需要注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近,换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISOC标准的代码,但不是全部。事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被gcc发现并提出警告。
  • 除了-pedantic之外,gcc还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall。
  • 在处理警告方面,另一个常用的编译选项是-Werror,它要求gcc将所有的警告当成错误进行处理,这在使用自动编译工具(如Make等)时非常有用。

优化gcc

  • 代码优化指的是编译器通过分析源代码,找出其中尚未达到最优的部分,然后对其重新进行组合,自的是改善程序的执行性能。gcC提供的代码优化功能非常强大,它通过编译选项-On来控制优化代码的生成,其中n是一个代表优化级别的整数。对于不向版本的gcc来讲,n的取值范围及其对应的优化效果可能并不完全相同,比较典型的范围是从0变化到2或3。
  • 编译时使用选项-O可以告诉gcc同时减小代码的长度和执行时间,其效果等价于-O1。在这一级别上能够进行的优化类型虽然取决于目标处理器,但一般都会包括线程跳转(Thread Jump)延迟退栈(Deferred Stack Pops)两种优化。选项-O2告诉qcc除了完成所有-O1i级别的优化之外,同时还要进行一些额外的调整i作,如处理器指令调度等。选项-O3则除了完成所有-O2级别的优化之外,还包括循环展开和其它一些与处理器特性相关的优化工作。通常来说,数字越高优化的等级越高,向时也就意味着程序的运行速度越快。许多Linux程序员都喜欢使用-O2选项,因为它在优化长度、编译时间和代码大小之间,取得了一个比较理想的平衡点。

一段效率很低的代码:inefficient_code.c

#include <stdio.h>
int main(void){
	unsigned long int counter;	/*定义相关的变量*/
	unsigned long int result;
	unsigned long int temp;unsigned int five;
	int i;
	for (counter = 0;counter<2009*20000/4+2010; 		counter += (10-6)/4){
		temp = counter / 1979;
		for(i=O; i< 20; i++)
			five=200*200/8000;/*每一次for循环都会进行复杂的计算*/
			result = counter;
}
	printf("Result is %ld\n", result);
	return 0;
}

命令:

gcc inefficient_code.c -o inefficient_code
time ./inefficient_code

gcc优化命令:

gcc -O inefficient_code.c -o inefficient_code
time ./inefficient_code
gcc -O2 inefficient_code.c -o inefficient_code
time ./inefficient_code
gcc -O3 inefficient_code.c -o inefficient_code
time ./inefficient_code

调试选项

  • 默认情况下,gcc在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的天小。如果需要在编译时生成调试符号信息,可以使用gcc的-g或者-ggdb选项。
  • gcc在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,凶此只能够用于回溯跟踪和堆栈转储之用。
  • gcc产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是gcc,那么还可以通过-ggdb选项在生成的二进制代码中包含gdb专用的调试信息。这种做法的优点是可以芳便gdb的调试工作,但缺点是可能导致其它调试需(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,它们对输出的调试符号有着相同的影响。
  • 使用任何一个调试选项都会使最终生成的二进制文件的天小急剧增加,向时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。这里还是以inefficient _code.c为例。

gdb调试器

Linux下的GDB(GNU Debugger)是一个用来调试C、C++程序的功能强大的调试器,它能够在程序运行的过程中观察程序的丙部结构和内存的便用情况。程序员也可以使用gdb来跟踪程序中的错误,从而减少了程序员的工作量。
一般来说,gdb主要提供以下功能:

  • 设置断点(断点可以是条件表达式),使程序在指定的代码行上暂停执行,便于观察;
  • 单步执行程序,便于调试;查看程序中变量值的变化;
  • 动态改变程序的执行环境;
  • 分析崩溃程序产生的core文件。

gdb是一个命令行方式的调试工具,它不同于我们在Windows下常见的Turbo c, vC等图形化程序开发工具。gdb的使用非常简单,只要在Linux的命令提示符下输入
gdb,系统便会启动gdb。也可以在gdb后面给出文件名,直孩指定想耍调试的程序,gdb就会首动调用这个可执行文件进行调试。命令形式如下:

#gdb filename

告诉gdb装入名为filename的可执行文件进行调试。
另外,为了使gdb正常工作,必须使程序在编译的时候包含调试信息,这需要在gcc编译时加上-g或者-ggdb选项。调试信息包含了程序中的每个变量的类型和在可执行文件中的地址映射以及源代码的行号。而gdb正是利用这些信息使源代码和机器码相关联。调试信息包含了程序中的每个变量的类型和在可执行文件中的地址映射以及源代码的行号。而gdb正是利用这些信息使源代码和机器码相关联。

gdb常用命令

命令含义描述
file(常用)装入想要调试的可执行文件
run(常用)执行当前被调试的程序
kill(常用)终止正在调试的程序
step(常用)执行一行源代码而且进入函数内部
next(常用)执行一行源代码但不进入函数内部
break(常用)在代码里设置断点,这将使程序执行到这里时被挂起
print(常用)打印表达式或变童的值,或打印内存中某个变量开始的一段连续区域的值,还以用来对变量进行赋值
display设置自动显示的表达式或变量,当程序停住或在单步跟踪时,这些变量会自动显示其当前值
list列出产生执行文件的源代码的一部分
quit退出gdb
watch使你能监视一个变量的值而不管它何时被改变
backtrace回溯跟踪
frame n定位到发生错误的代码段,n为 backtrace命令的输出结果中的行号
examine查看内存地址中的值
jump使程序跳转执行
signal产生信号
return强制函数返回
call强制调用函救
make使用户不遇出gdb就可以重新产生可执行文件
shell使用户不离开gdb就执行Linux 的shell命令

gdb调试初步

一个简单的例子认识gdb调试:

//simple_gdb.c
#include <stdio.h>
int main(void)
{
	int input = 0;
	printf("Input an integer:");
	scanf("%d", input);	/*这里出现了错误*/
	printf("The integer you input is %d\n", input); 
	return 0;
}

使用gcc编译上述代码,并加上-ggdb3调试选项。

gcc -ggdb3 simple_gdb.c -o simple_gdb
# 调试信息
gdb simple_gdb
run # 运行
backtrace # 追踪错误
frane 3 # 查看backtrace中的前3行

gdb的使用详解

通过使用gdb逐步调试代码,可以看到程序内部是如何运行的,还可以查看程序中变量的值、内存使用情况、栈信息以及其他一些细节问题。
下面通过一个实例介绍gdb调试的具体步
骤,读者通过此程序可以学到怎样跟踪程序代码,并掌握如何运用–些技巧来调试程序
源代码如下所示。计算两个整数的平方和:

//square_ sum.c
#include <stdio.h>
int calculate(int x,int y);
int main(void)
{
	int num_ 1,num_ _2,result;
	while(1)/*使用死循环,使程序可以- - 直接收终端的输入*/
	{
	printf("Enter two integers,or use 0 0 to exit:");
	scanf("%d %d" ,&num_ .1,&num_ _2); /*输入两个整数*/
	if (num_1==0 && num_2 == 0) /*两个 整数均为0时退出*/
		exit(0);
	result=calculate(num_1 ,num_2);	/*调用calculate丽数进行计算*/
	printf("The result is:%d\n",result);	/*输出结果*/
	}
	return 0;
}
int calculate(int x,int y)
{
	int res; .
	res=x*x+ y*y;
	return res;
}

命令:

gcc -ggdb3 -o square_sum square_sum
gdb square_sum

break命令的用法

命令含义描述
break在进入指定函数时停住。C++中可以使用class::function或function(type,type)格式来指定函数名
break <linenum>在指定行号停住
break +offset在当前行号的前面的offset行停住。offset为自然数
break -offset在当前行号的后面的offset行停住
break filename:linenum在源文件filename的linenum行处停住
break filename:function在源文件filename的function函数的入口处停住
break *address在程序运行的内存地址处停住
break该命令没有参数时,表示在下一条指令处停住
break … if<condition>condition表示条件,在条件成立时停住。比如在循环体中,可以设置break if i = 100,表示当i为100时停住程序
break main # 设置断点
r(un) # 运行
s(tep) # 单步调试

查看运行时数据

在调试程序的过程中,往往需要查看程序中某些表达式或变量的值,以判断程序运行是否正确。使用gdb调试时,常用到的是printdisplay命令,以及查看内存、寄存器的信息等。

  1. print命令

在调试程序时,当程序被停住时,可以使用print命令(简写为p),或是同义命令inspect来查看当前程序的运行数据。print命令的格式是:

print <expr>
print /<f> <expr>

<expr>是表达式,是所调试程序的语言的表达式(gdb可以调试多种编程语言);<f>是输出的格式。比如,茹果要把表达式按16进制的格式输出,那么就是

  1. 自动显示命令display

可以设置一些自动显示的变量,当程序停住时,或是在单步跟踪时,这些变量会自动显示。相关的gdb命令是display,格式如下:

display <expr>
display/<fmt> <expr>
display/<fmt> <addr>

expr是一个表达式,fmt表示显示的格式,addr表示内存地址。当用display设定好了一个或多个表达式后,只要程序停下来,gdb会自动显示所设置的这些表达式的值。
格式i和s同样被display支持。

和display相关的gdb命令:

命令含义描述
undisplay<dnums…>
delete display <dnums…>
删除自动显示,dnums为已设置好了的自动显示的编号。如果要同时删除几个编号,可以用空格分隔;如果要删除一个范围内的编号,可以用减号表示(如:2-5)
disable display <dnums…>
enable display<dnums…>
不删除自动显示的设置,而只是让其失效或恢复
info display查看 display设置的自动显示的信息。gdb会显示出一张表格,报告调试中设置了多少个自动显示设置,其中包括已设置的编号、表达式及是否enable等

查看源程序

在程序调试的过程中,有时候需要查看源程序的内容,以及源代码在内存中的情况,本节向读者介绍与此相关的gdb命令。
1.显示源代码

gdb 可以打印出所调试程序的源代码,当然,在程序编译时一定要加上-g参数,把源程序信息编译到执行文件中,不然就看不到源程序了。当程序停下来以后,gdb会报告程序停在了程序的第几行上。可以用list命令来显示程序的源代码,如下表所示。

命令含义描述
list <linenum>显示程序第linenum行的周围的源程序
list <function>显示函数名为function的函数的源程序
list显示当前行后面的源程序
list -显示当前行前面的源程序。一般是显示当前行的上5行和下5行,或者显示当前行的上2行和下8行,默认共显示10行
set listsize <count>设置一次显示源代码的行数
show listsize查看当前listsize的设置
list <first> ,<last>显示从first行到last行之间的源代码
list ,<last>显示从当前行到last行之间的源代码
list +向后显示源代码
  1. 源代码的内存

可以使用info line命令来查看源代码在内存中的地址。和大多数gdb命令相同,info line后面也可以跟“行号”、“函数名”、“文件名:行号”、“文件名:函数名”的参数形式,这个命令会显示出所指定的源码在运行时的内存地址,来看一下程序中的子函数calculate()在内存中的地址。

info line calculate

还有一个命令disassemble,可以查看源程序的当前执行时的机器码,这个命令会把目前内存中的指令dump出来。如下面的示例表示查看函数calculate()的汇编代码:

disassemble calciate

改变程序的执行

一旦使用gdb挂上被调试程序,当程序运行起来后,可以根据自己的调试思路来动态地在gdb中更改当前被调试程序的运行线路或是其变量的值。这个强大的功能能够让用户更好地调试程序。比如,可以在程序的一次运行中走遍程序的所有分支。

  1. 修改变量值

print命令还有一个功能是,修改被调试程序中运行时的变量值,比如:

(gdb) print x=8

x=8这个表达式是C/C++的语法,意为把变量x的值修改为8,如果当前调试的语言是Pascal,那么可以使用Pascal的语法x:=8。

  1. 跳转执行

一般来说,被调试程序会按照程序代码的运行顺序依次执行。gdb提供了乱序执行的功能,也就是说,gdb可以修改程序的执行顺序,可以让程序执行随意跳跃。这个功能可以由gdb的jump命令来实现:

jump <linespec>

指定下一条语句的运行点。<linespce>可以是文件的行号,可以是file:line格式,可以是+num这种偏移量格式,表示下一条运行语句从哪里开始。

jump <address>

这里的<address>是代码行的内存地址。

  1. 产生信号量
    使用singal命令可以产生一个信号量给被调试的程序。如中断信号Ctrl+C。这非常方便于程序的调试,可以在程序运行的任意位置设置断点,并在该断点用gdb产生一个信号量。精确地在某处产生信号非常有利程序的调试。
    其语法是:
signal <singal>

Linux的系统信号量通常从1到15。所以的取值也在这个范围。
signal命令和shell的kill命令不同,系统的kill命令发信号给被调试程序时,是由gdb截获的,而signal命令所发出的信号则是直接发给被调试程序的。

  1. 强制函数返回

如果调试断点在某个函数中,还有语句没有执行完,可以使用return命令强制函数忽略还没有执行的语句并返回。

return
return <expression>

使用return命令取消当前函数的执行,并立即返回。如果指定了<expression>,那么该表达式的值会被当作函数的返回值。

  1. 强制调用函数

强制调用函数使用call命令,格式如下:

call <expr>

表达式中也可以是函数,以达到强制调用函数的目的,显示函数的返回值,如果函数返回值是void,那么就不显示。
另一个相似的命令也可以完成这一功能———print。 print后面可以跟表达式,所以也可以用它来调用函数。print和call的不同之处是,如果函数返回void,call则不显示,print则显示函数返回值,并把该值存入历史数据中。

xxgdb调试器简介

在Linux平台下调试C程序时,除了使用gdb之外,还可以使用xxgdb。xxgdb是X-Window系统的调试工具,实际上,xxgdb是gdb的图形界面版本,它保留了gdb的所有功能和特性。程序员可以像在VC、Torbo C上调试C程序那样,用单击按钮来代替输入命令,它还能显示当前断点设置的位置,而不用通过输入断点的名字来查询。对于已经习惯并且爱好使用图形界面的调试工具的用户,xxgd将是一个不错的选择。
要使用xxgdb完成调试功能,首先要对其进行初始化,用户可以使用gdb里任何有效的命令行选项来初始化xxgdb。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值