C語言中的調試小技巧


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\n", 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\n"
 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\n", 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.\n", __FUNCTION__, __FILE__, __LINE__, __TIME__, __DATE__, __STDC__? "with" : "without");

 

當我覺得可能是對某函數因為參數指針p是NULL而使得程序崩潰,那么我可以在該操作中加入如下一句:

1 if(!p)
2     BUG();

 

這樣如果真的因為p是NULL造成的程序崩潰的話,程序退出前會輸出這個BUG在源代碼中的位置,方便我們追蹤它。至於為什么要輸出編譯的時間和日期,有的時候我們修改了.h文件,而往往在Makefile中我們是不寫.h的依賴關系的,這樣就可能會造成某種不一致,這時候輸出代碼編譯的時間、日期就顯得很有用了。最后那個ANSI C的檢查,其實只是個以防萬一而已。

 
 
 
標簽:  調試


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM