• C语言中的调试小技巧


    经常看到有人介绍一些IDE或者像gdb这样的调试器的很高级的调试功能,也听人说过有些牛人做工程的时候就用printf来调试,不用特殊的调试器。特别是在代码经过编译器一些比较复杂的优化后,会变得“难以辨认”,使用调试器也变得有些头疼。先举个简单的例子:

     1 #include <stdio.h>
     2 
     3 int main(){
     4     int a[6], i, sum = 0;
     5     for(i = 0; i<6; i++)
     6         a[i] = i<<2;
     7     a[3] = 5;
     8     for(i = 0; i<6; i++)
     9         sum += a[i];
    10     printf("sum = %d
    ", sum);
    11     return 0;
    12 }

    如果采用gcc(笔者的版本是4.7.3)编译,使用

    1 gcc -O3 sum.c -S

    来编译,可以查看到编译出来的汇编代码是:

     1     .file    "sum.c"
     2     .section    .rodata.str1.1,"aMS",@progbits,1
     3 .LC0:
     4     .string    "sum = %d
    "
     5     .section    .text.startup,"ax",@progbits
     6     .p2align 4,,15
     7     .globl    main
     8     .type    main, @function
     9 main:
    10 .LFB24:
    11     .cfi_startproc
    12     subq    $40, %rsp
    13     .cfi_def_cfa_offset 48
    14     movl    $53, %edx
    15     movl    $.LC0, %esi
    16     movl    $1, %edi
    17     xorl    %eax, %eax
    18     call    __printf_chk
    19     xorl    %eax, %eax
    20     addq    $40, %rsp
    21     .cfi_def_cfa_offset 8
    22     ret
    23     .cfi_endproc
    24 .LFE24:
    25     .size    main, .-main
    26     .ident    "GCC: (Ubuntu/Linaro 4.7.3-1ubuntu1) 4.7.3"
    27     .section    .note.GNU-stack,"",@progbits

    说白了,就是gcc直接将main()优化成了这样:

    1 int main(){
    2     printf("sum = %d
    ", 53);
    3     return 0;
    4 }

    可想而知,对于这样优化的代码,调试器也会抓狂。

    那么采用printf大法的好处就出来了,无论编译器如何优化,printf的输出总是正确的(编译器的优化总是保证程序效果不变),而且相较于调试器各种高深摸测的命令,printf的用法是程序猿的必备知识,所以利用printf来跟踪程序有的时候比调试器还要方便。虽然有的时候printf可能显得不那么安全,但你可换其它的安全的输出函数啊。其实printf大法的实质就是输出大法,直接在程序(当然是debug版的,或者说verbose功能下,release版当然…你懂的…)运行的时候屏显各种希望获取的运行时信息。

    如何printf一个变量的值,我就不多说了,毕竟这是咱们程序猿的基本功。我是想要介绍一些调试用的宏:

    宏名(每个宏名前后双下划线) 类型 意义
    __FILE__ 字符串 当前程序名
    __FUNCTION__ 字符串 当前函数名
    __LINE__ 整数 当前行号(在源代码中的)
    __DATE__ 字符串 被编译的日期
    __TIME__ 字符串 被编译的时间
    __STDC__ 整数(布尔) 如果编译器按照ANSI C来编译,为非零值;否则为0

    使用这些宏来配合printf,可以做到很好的调试(当然也可以去做条件编译,不过本文暂不讨论这方面的应用)。

    比如我可以定义一个BUG()如下:

    1 #define BUG()    printf("Bug in function: %s (file: %s), @line: %d. It is compiled on %s  %s, %s ANSI C standard.
    ", __FUNCTION__, __FILE__, __LINE__, __TIME__, __DATE__, __STDC__? "with" : "without");

    当我觉得可能是对某函数因为参数指针p是NULL而使得程序崩溃,那么我可以在该操作中加入如下一句:

    1 if(!p)
    2     BUG();

    这样如果真的因为p是NULL造成的程序崩溃的话,程序退出前会输出这个BUG在源代码中的位置,方便我们追踪它。至于为什么要输出编译的时间和日期,有的时候我们修改了.h文件,而往往在Makefile中我们是不写.h的依赖关系的,这样就可能会造成某种不一致,这时候输出代码编译的时间、日期就显得很有用了。最后那个ANSI C的检查,其实只是个以防万一而已。

  • 相关阅读:
    行列式学习笔记
    二项式反演学习笔记
    【AtCoder】ARC096(C
    【LOJ】#2127. 「HAOI2015」按位或
    [ACM] POJ 1218 THE DRUNK JAILER (关灯问题)
    lua的弱弱引用表
    西班牙式软件团队
    【DRP】採用dom4j完毕XML文件导入数据库
    基于Linux平台病毒Wirenet.c解析
    【剑指offer】异或去重
  • 原文地址:https://www.cnblogs.com/FreeBirdLjj/p/3250771.html
Copyright © 2020-2023  润新知