zoukankan      html  css  js  c++  java
  • GCC精彩之旅_2(转)

    说明: 本文共两篇,转自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 ", 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程序员的起跑线

  • 相关阅读:
    UVa 122 Trees on the level
    UVa 623 500!
    UVa 424 Integer Inquiry
    UVa 10082 WERTYU
    关于c语言的输入输出
    (转)提问的智慧for oracle
    根据输入的用户ID串,返回用户名字串:TRIM函数的使用
    转:Oracle数据库一致性读的原理(Consistent Read)
    Instr()函数的使用计算字符串中出现某个字母或单词的个数
    RETURNING的使用:
  • 原文地址:https://www.cnblogs.com/space-place/p/8109478.html
Copyright © 2011-2022 走看看