说明: 本文共两篇,转自GCC精彩之旅。第一篇着重介绍GCC编译一个程序的过程与优化,第二篇侧重在GCC结合GDB对代码的调试。

调试

    一个功能强大的调试器不仅为程序员提供了跟踪程序执行的手段,而且还可以帮助程序员找到解决问题的方法。对于Linux程序员来讲,GDB(GNU Debugger)通过与GCC的配合使用,为基于Linux的软件开发提供了一个完善的调试环境。

   
默认情况下,GCC在编译时不会将调试符号插入到生成的二进制代码中,因为这样会增加可执行文件的大小。如果需要在编译时生成调试符号信息,可以使用GCC的-g或者-ggdb选项。GCC在产生调试符号时,同样采用了分级的思路,开发人员可以通过在-g选项后附加数字1、2或3来指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括扩展的符号表、行号、局部或外部变量信息。级别3(-g3)包含级别2中的所有调试信息,以及源代码中定义的宏。级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。

   
GCC产生的调试符号具有普遍的适应性,可以被许多调试器加以利用,但如果使用的是GDB,那么还可以通过-ggdb选项在生成的二进制代码中包含GDB专用的调试信息。这种做法的优点是可以方便GDB的调试工作,但缺点是可能导致其它调试器(如DBX)无法进行正常的调试。选项-ggdb能够接受的调试级别和-g是完全一样的,它们对输出的调试符号有着相同的影响。

    需要注意的是,使用任何一个调试选项都会使最终生成的二进制文件的大小急剧增加,同时增加程序在执行时的开销,因此调试选项通常仅在软件的开发和调试阶段使用。调试选项对生成代码大小的影响从下面的对比过程中可以看出来:

# gcc optimize.c -o optimize

# ls optimize -l

-rwxrwxr-x  1 xiaowp   xiaowp  11649 Nov 20 08:53 optimize  (未加调试选项)

# gcc -g optimize.c -o optimize

# ls optimize -l

-rwxrwxr-x  1 xiaowp   xiaowp  15889 Nov 20 08:54 optimize  (加入调试选项)
 

虽然调试选项会增加文件的大小,但事实上Linux中的许多软件在测试版本甚至最终发行版本中仍然使用了调试选项来进行编译,这样做的目的是鼓励用户在发现问题时自己动手解决,是Linux的一个显著特色。

下面还是通过一个具体的实例说明如何利用调试符号来分析错误,所用程序见清单4所示。

    清单4:crash.c

#include

int main(void)

{

  int input =0;

  printf("Input an integer:");

  scanf("%d", input);

  printf("The integer you input is %d\n", input);

  return 0;

}
 

编译并运行上述代码,会产生一个严重的段错误(Segmentation fault)如下:

# gcc -g crash.c -o crash

# ./crash

Input an integer:10

Segmentation fault
 

为了更快速地发现错误所在,可以使用GDB进行跟踪调试,方法如下:

# gdb crash

GNU gdb Red Hat Linux (5.3post-0.20021129.18rh)

……

(gdb)
 

当GDB提示符出现的时候,表明GDB已经做好准备进行调试了,现在可以通过run命令让程序开始在GDB的监控下运行:

(gdb) run

Starting program: /home/xiaowp/thesis/gcc/code/crash

Input an integer:10

 

Program received signal SIGSEGV, Segmentation fault.

0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6
 

仔细分析一下GDB给出的输出结果不难看出,程序是由于段错误而导致异常中止的,说明内存操作出了问题,具体发生问题的地方是在调用_IO_vfscanf_internal
( )的时候。为了得到更加有价值的信息,可以使用GDB提供的回溯跟踪命令backtrace,执行结果如下:

(gdb) backtrace

#0  0x4008576b in _IO_vfscanf_internal () from /lib/libc.so.6

#1  0xbffff0c0 in ?? ()

#2  0x4008e0ba in scanf () from /lib/libc.so.6

#3  0x08048393 in main () at crash.c:11

#4  0x40042917 in __libc_start_main () from /lib/libc.so.6
 

跳过输出结果中的前面三行,从输出结果的第四行中不难看出,GDB已经将错误定位到crash.c中的第11行了。现在仔细检查一下:

(gdb) frame 3

#3  0x08048393 in main () at crash.c:11

11       scanf("%d", input);
 

使用GDB提供的frame命令可以定位到发生错误的代码段,该命令后面跟着的数值可以在backtrace命令输出结果中的行首找到。现在已经发现错误所在了,应该将

scanf("%d", input);

改为

scanf("%d", &input);
 

完成后就可以退出GDB了,命令如下:

(gdb) quit
 

GDB的功能远远不止如此,它还可以单步跟踪程序、检查内存变量和设置断点等。

   
调试时可能会需要用到编译器产生的中间结果,这时可以使用-save-temps选项,让GCC将预处理代码、汇编代码和目标代码都作为文件保存起来。如果想检查生成的代码是否能够通过手工调整的办法来提高执行性能,在编译过程中生成的中间文件将会很有帮助,具体情况如下:

# gcc -save-temps foo.c -o foo

# ls foo*

foo  foo.c  foo.i  foo.s
 

GCC支持的其它调试选项还包括-p和-pg,它们会将剖析(Profiling)信息加入到最终生成的二进制代码中。剖析信息对于找出程序的性能瓶颈很有帮助,是协助Linux程序员开发出高性能程序的有力工具。在编译时加入-p选项会在生成的代码中加入通用剖析工具(Prof)能够识别的统计信息,而-pg选项则生成只有GNU剖析工具(Gprof)才能识别的统计信息。

最后提醒一点,虽然GCC允许在优化的同时加入调试符号信息,但优化后的代码对于调试本身而言将是一个很大的挑战。代码在经过优化之后,在源程序中声明和使用的变量很可能不再使用,控制流也可能会突然跳转到意外的地方,循环语句有可能因为循环展开而变得到处都有,所有这些对调试来讲都将是一场噩梦。建议在调试的时候最好不使用任何优化选项,只有当程序在最终发行的时候才考虑对其进行优化。

   上次的培训园地中介绍了GCC的编译过程、警告提示功能、库依赖、代码优化和程序调试六个方面的内容。这期是最后的一部分内容。

加速

    在将源代码变成可执行文件的过程中,需要经过许多中间步骤,包含预处理、编译、汇编和连接。这些过程实际上是由不同的程序负责完成的。大多数情况下GCC可以为Linux程序员完成所有的后台工作,自动调用相应程序进行处理。

   
这样做有一个很明显的缺点,就是GCC在处理每一个源文件时,最终都需要生成好几个临时文件才能完成相应的工作,从而无形中导致处理速度变慢。例如,GCC在处理一个源文件时,可能需要一个临时文件来保存预处理的输出、一个临时文件来保存编译器的输出、一个临时文件来保存汇编器的输出,而读写这些临时文件显然需要耗费一定的时间。当软件项目变得非常庞大的时候,花费在这上面的代价可能会变得很沉重。

    解决的办法是,使用Linux提供的一种更加高效的通信方式—管道。它可以用来同时连接两个程序,其中一个程序的输出将被直接作为另一个程序的输入,这样就可以避免使用临时文件,但编译时却需要消耗更多的内存。

    在编译过程中使用管道是由GCC的-pipe选项决定的。下面的这条命令就是借助GCC的管道功能来提高编译速度的:

# gcc -pipe foo.c -o foo
 

在编译小型工程时使用管道,编译时间上的差异可能还不是很明显,但在源代码非常多的大型工程中,差异将变得非常明显。

文件扩展名

    在使用GCC的过程中,用户对一些常用的扩展名一定要熟悉,并知道其含义。为了方便大家学习使用GCC,在此将这些扩展名罗列如下:

.c C原始程序;

.C C++原始程序;

.cc C++原始程序;

.cxx C++原始程序;

.m Objective-C原始程序;

.i 已经过预处理的C原始程序;

.ii 已经过预处理之C++原始程序;

.s 组合语言原始程序;

.S 组合语言原始程序;

.h 预处理文件(标头文件);

.o 目标文件;

.a 存档文件。

GCC常用选项

    GCC作为Linux下C/C++重要的编译环境,功能强大,编译选项繁多。为了方便大家日后编译方便,在此将常用的选项及说明罗列出来如下:

-c 通知GCC取消链接步骤,即编译源码并在最后生成目标文件;

-Dmacro 定义指定的宏,使它能够通过源码中的#ifdef进行检验;

-E 不经过编译预处理程序的输出而输送至标准输出;

-g3 获得有关调试程序的详细信息,它不能与-o选项联合使用;

-Idirectory 在包含文件搜索路径的起点处添加指定目录;

-llibrary 提示链接程序在创建最终可执行文件时包含指定的库;

-O、-O2、-O3 将优化状态打开,该选项不能与-g选项联合使用;

-S 要求编译程序生成来自源代码的汇编程序输出;

-v 启动所有警报;

-Wall 在发生警报时取消编译操作,即将警报看作是错误;

-Werror 在发生警报时取消编译操作,即把报警当作是错误;

-w 禁止所有的报警。

小结

   
GCC是在Linux下开发程序时必须掌握的工具之一。本文对GCC做了一个简要的介绍,主要讲述了如何使用GCC编译程序、产生警告信息、调试程序和加快GCC的编译速度。对所有希望早日跨入Linux开发者行列的人来说,GCC就是成为一名优秀的Linux程序员的起跑线

GCC精彩之旅_2(转)的更多相关文章

  1. GCC精彩之旅_1

    说明: 本文共两篇,转自GCC精彩之旅.第一篇着重介绍GCC编译一个程序的过程与优化,第二篇侧重在GCC结合GDB对代码的调试. 在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎 ...

  2. GCC精彩之旅

    在为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器.目前Linux下最常用的C语言编译器是GCC(GNU Compile ...

  3. C++程序设计之四书五经[转自2004程序员杂志]--下篇

    C++程序设计之四书五经(下篇) 作者:荣耀 我在上篇中“盘点”了TCPL和D&E以及入门教程.高效和健壮编程.模板和泛型编程等方面共十几本C++好书.冬去春来,让我们继续C++书籍精彩之旅. ...

  4. Unity3D for iOS初级教程:Part 3/3

    转自Unity 3D for iOS 这篇文章还可以在这里找到 英语 Learn how to use Unity to make a simple 3D iOS game! 这份教程是由教程团队成员 ...

  5. Unity3D for iOS初级教程:Part 3/3(下)

    转自:http://www.cnblogs.com/alongu3d/archive/2013/06/01/3111739.html 消息不会自动消除 你基本的游戏功能已经完成了,但是显示一些关于游戏 ...

  6. SAM4E单片机之旅——23、在AS6(GCC)中使用FPU

    浮点单元(Floating Point Unit,FPU),是用于处理浮点数运算的单元. 为使用FPU,除了需要启用FPU外,还需要对编译器进行设置,以使其针对浮点运算生成特殊的指令.虽然在Atmel ...

  7. 【Linux探索之旅】开宗明义+第一部分第一课:什么是Linux?

    内容简介 1.课程大纲 2.第一部分第一课:什么是Linux? 3.第一部分第二课预告:下载Linux,免费的噢!   开宗明义 我们总听到别人说:Linux挺复杂的,是给那些追求逼格的程序员用的.咱 ...

  8. 使用 GCC 和 GNU Binutils 编写能在 x86 实模式运行的 16 位代码

    不可否认,这次的标题有点长.之所以把标题写得这么详细,主要是为了搜索引擎能够准确地把确实需要了解 GCC 生成 16 位实模式代码方法的朋友带到我的博客.先说一下背景,编写能在 x86 实模式下运行的 ...

  9. Linux 部署ASP.NET SQLite 应用 的坎坷之旅 附demo及源码

    Linux 部署ASP.NET SQLite 应用 的坎坷之旅.文章底部 附示例代码. 有一台闲置的Linux VPS,尝试着部署一下.NET 程序,结果就踏上了坑之路,不过最后算是完美解决问题,遂记 ...

随机推荐

  1. c#中RGB与int类型之间的转换

    Color color = Color.FromArgb(0, 0, 255);int colorInt = ParseRGB(color); --------------------- int Pa ...

  2. Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式。

    时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...

  3. Javascript学习--时间

    digit = [ [ [0,0,1,1,1,0,0], [0,1,1,0,1,1,0], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1,1,0,0,0,1,1], [1, ...

  4. JS实现倒计时

    HTML部分: <div class="div"> <div id="div"> </div> </div> C ...

  5. 自己写的日志框架--linkinLog4j--框架可配置+提性能

    OK,上一篇博客我们已经实现了日志框架的基本的功能,但是还有一个最大的问题就是日志输出地不能重定向,然后一些输出也不可控.那现在我们来实现一个比较完整的日志框架. 设计思路如下: 1,定义一堆常量Li ...

  6. Can’t open /dev/* exclusively. Mounted filesystem?解决

    1 错误提示:Can’t open /dev/* exclusively. Mounted filesystem? 做完软件RAID之后,根据鸟哥书上的操作,其实没有真正删除软件RAID,导致出现如下 ...

  7. Linux指令--ping

    Linux系统的ping命令是常用的网络命令,它通常用来测试与目标主机的连通性,我们经常会说"ping一下某机器,看是不是开着".不能打开网页时会说"你先ping网关地址 ...

  8. java常用类--与用户互动

    运行java的参数: 主方法:public static void main(String[] args){}:为了让JVM可以自由调用main方法,使用public修饰,JVM通过类来调用main方 ...

  9. 异常org.xml.sax.SAXParseException; lineNumber: 5; columnNumber: 11; 注释中不允许出现字符串 "--"。的原因

    是由于编码格式不统一导致的. 把ecplise的workpace的编码改成utf-8

  10. CentOS 通过yum来升级php到php5.6

    在文章中,我们将展示在centOS系统下如果将php升级到5.6,之前通过yum来安装lamp环境,直接升级的话,提示没有更新包,也就是说默认情况下php5.3.3是最新 1.查看已经安装的php版本 ...