C 語言中常用的調試技巧和 demo
C語言中常用的調試方法
打印調試信息
GDB 調試器
編寫單元測試
段錯誤產生原因
初學時兩種常用的段錯誤調試方法
C 語言中常用的調試技巧和 demo
當程序員進行調試時,他們通常會使用一些調試語句或技巧來幫助他們理解代碼的執行過程以及識別問題。以下是一些在 C 語言中常用的調試技巧和 demo:
1. 使用 printf
進行調試
#include <stdio.h>int main() {int a = 5;printf("Value of a: %d\n", a);// ...return 0;
}
2. 使用 assert
進行斷言
#include <assert.h>int divide(int a, int b) {assert(b != 0);return a / b;
}
3. 使用 gdb
進行命令行調試
gcc -g -o my_program my_program.c
gdb ./my_program
在 GDB 中可以使用諸如 break
, run
, print
, step
, backtrace
等命令進行調試。
4. 輸出變量地址
#include <stdio.h>int main() {int a = 5;printf("Address of a: %p\n", (void*)&a);// ...return 0;
}
5. 使用 #ifdef
和宏定義進行條件編譯
#include <stdio.h>#define DEBUG 1int main() {#ifdef DEBUGprintf("Debugging information\n");#endif// ...return 0;
}
6. 手動觸發程序崩潰
#include <stdio.h>
#include <stdlib.h>int main() {int* ptr = NULL;*ptr = 5; // This will cause a segmentation fault// ...return 0;
}
7. 使用 errno
打印錯誤信息
#include <stdio.h>
#include <errno.h>int main() {FILE *file = fopen("nonexistent_file.txt", "r");if (file == NULL) {perror("Error opening file");}// ...return 0;
}
8. 使用 valgrind
進行內存檢查
valgrind ./my_program
Valgrind 可以檢測內存泄漏和其他內存錯誤。
這些都是簡單而有效的調試技巧,可以幫助你更好地理解代碼并找到潛在問題。
C語言中常用的調試方法
在C語言中,常用的調試方法包括打印調試信息、使用調試器和編寫單元測試等。下面是一些用于調試的示例代碼:
- 打印調試信息
#include <stdio.h>// 在代碼中插入打印語句
void example_function() {printf("Debug: This is a debug message.\n");// ... 其他代碼 ...
}
- 條件打印
#include <stdio.h>// 使用條件打印語句
#ifdef DEBUG
#define DEBUG_PRINT(fmt, args...) printf(fmt, ##args)
#else
#define DEBUG_PRINT(fmt, args...) // 空定義,即不執行任何操作
#endifvoid example_function() {DEBUG_PRINT("Debug: This is a debug message.\n");// ... 其他代碼 ...
}
在編譯時,可以通過定義或未定義 DEBUG
宏來控制是否打印調試信息。
- 使用
assert
斷言
#include <assert.h>void example_function(int x) {assert(x > 0 && "x must be greater than 0");// ... 其他代碼 ...
}
assert
宏用于檢查一個表達式是否為真,如果為假,則觸發一個斷言失敗。
- 使用調試器(GDB)
#include <stdio.h>int main() {int x = 10;int y = 0;// 計算除法,可能導致除零錯誤int result = x / y;printf("Result: %d\n", result);return 0;
}
在終端中使用 GDB 調試程序:
gcc -g -o my_program my_program.c
gdb my_program
在 GDB 中運行程序,當程序崩潰時,可以使用 backtrace
和其他命令來查看調用棧和變量值。
- 編寫單元測試
使用測試框架進行單元測試,例如 C 中的 Unity 框架。編寫測試用例來驗證函數的預期行為。
#include "unity.h"void test_addition() {TEST_ASSERT_EQUAL(5, add(2, 3));
}int main() {UNITY_BEGIN();RUN_TEST(test_addition);return UNITY_END();
}
以上示例代碼提供了一些常見的調試方法。選擇合適的方法取決于問題的性質和復雜性。
打印調試信息
在C語言中,開發者經常使用printf
語句進行調試。下面是一些在不同情境下用于調試的printf
語句的示例:
-
打印變量值:
int x = 42; printf("The value of x is: %d\n", x);
-
調試文件、函數、行號:
printf("%s|%s|%d\n", __FILE__, __func__, __LINE__);
-
打印字符串:
char message[] = "Hello, World!"; printf("Message: %s\n", message);
-
打印指針地址:
int *ptr = NULL; printf("Pointer address: %p\n", (void*)ptr);
-
條件打印:
#ifdef DEBUGprintf("Debug information\n"); #endif
-
格式化輸出:
double pi = 3.14159265359; printf("Value of pi: %.2f\n", pi); // 保留兩位小數
-
調試標志:
#define DEBUG 1#if DEBUGprintf("Debugging is enabled\n"); #endif
-
打印多個變量:
int a = 10, b = 20; printf("Values: a=%d, b=%d\n", a, b);
-
打印特殊字符:
printf("This is a newline: \n");
-
查看函數執行流程:
printf("Entering function...\n"); // Function code printf("Leaving function...\n");
這些語句可以根據需要添加或組合,根據代碼的結構和特定的調試需求選擇合適的調試語句。在實際的開發中,有時候也會使用專門的調試工具,比如gdb
(GNU Debugger)等。
GDB 調試器
GDB(GNU Debugger)是一款強大的調試器,可以用于調試C、C++等程序。以下是一些基本的 GDB 使用方法:
編譯程序以便調試
在編譯程序時,需要包含調試信息。使用 -g
選項告訴編譯器生成調試信息:
gcc -g -o my_program my_program.c
啟動 GDB
gdb ./my_program
基本命令
-
運行程序
run
或者可以帶參數:
run arg1 arg2
-
設置斷點
在函數或者某一行設置斷點:
break function_name break file_name:line_number
-
執行程序直到斷點
continue
-
單步執行
next # 執行一行代碼,不進入函數 step # 執行一行代碼,進入函數
-
查看變量值
print variable_name
-
查看函數調用棧
backtrace
-
跳轉到某一行
jump line_number
-
終止程序
kill
示例
考慮以下簡單的程序:
// my_program.c
#include <stdio.h>int main() {int x = 5;int y = 0;int result = x / y;printf("Result: %d\n", result);return 0;
}
使用 GDB 調試:
-
編譯程序:
gcc -g -o my_program my_program.c
-
啟動 GDB:
gdb ./my_program
-
在 GDB 中運行程序:
run
程序將在除以零的地方崩潰。
-
查看變量值:
print x print y
-
查看函數調用棧:
backtrace
-
設置斷點并運行:
break main run
在
main
函數的入口設置了斷點,程序會在進入main
函數時停下。 -
單步執行:
step
逐行執行代碼,可以看到在除以零的地方停下。
-
退出 GDB:
quit
這只是 GDB 的基本用法。對于更復雜的程序,可能需要更多高級的調試技術。 GDB 的命令和功能非常豐富,可以根據需要查閱 GDB 的文檔。
編寫單元測試
編寫單元測試是一種驗證代碼的方法,以確保每個獨立單元的功能都按預期工作。下面是一份簡要的指南,幫助你編寫 C 語言程序的單元測試。
選擇測試框架
在 C 語言中,一些流行的測試框架包括:
- Check: Check Testing Framework
- Unity: Unity Testing Framework
- Google Test: Google Test
安裝測試框架
下載并按照測試框架的文檔安裝框架。通常,這涉及到將測試框架的頭文件和庫文件添加到你的項目中。
編寫測試用例
使用測試框架編寫測試用例。測試用例是檢查特定函數或模塊的代碼塊,確保其行為符合預期。
示例測試用例(使用 Unity 框架):
#include "unity.h"
#include "my_functions.h" // 你的代碼文件頭文件void test_addition() {TEST_ASSERT_EQUAL_INT(5, add(2, 3));
}void test_subtraction() {TEST_ASSERT_EQUAL_INT(2, subtract(5, 3));
}int main() {UNITY_BEGIN();RUN_TEST(test_addition);RUN_TEST(test_subtraction);UNITY_END();return 0;
}
編寫被測試的代碼
在編寫測試用例之前,首先編寫要測試的代碼。例如,你可能有一個包含兩個函數的頭文件 my_functions.h
:
// my_functions.h
#ifndef MY_FUNCTIONS_H
#define MY_FUNCTIONS_Hint add(int a, int b);
int subtract(int a, int b);#endif
對應的實現文件 my_functions.c
可能如下所示:
// my_functions.c
#include "my_functions.h"int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}
構建測試
構建測試時,需要鏈接測試框架和被測試的代碼。
gcc -o my_tests my_tests.c my_functions.c -lunity
運行測試
./my_tests
測試框架將執行所有測試用例,并顯示每個測試用例的結果。
分析測試結果
測試通過時,你會看到一條消息表明所有測試用例都通過。如果有測試失敗,框架通常會提供詳細的輸出,說明哪個測試用例失敗以及失敗的原因。
增加測試覆蓋率
編寫測試用例時,盡量涵蓋不同的輸入情況,以確保代碼的魯棒性。覆蓋率工具(例如 gcov)可以幫助你確定代碼中哪些部分被測試覆蓋。
持續集成
將單元測試集成到你的持續集成(CI)流程中,以確保每次代碼更改都會觸發測試。這有助于捕獲引入錯誤的更改,并確保代碼庫的整體穩定性。
以上是一個簡單的單元測試流程,實際情況可能因項目的復雜性而有所不同。
段錯誤產生原因
1.訪問不存在的內存地址
如下面代碼,ptr沒有申請空間就直接拷貝數據:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr沒有申請空間就直接使用//The right way://ptr = (char *)malloc(sizeof(char) * 10);//memset(ptr, 0, 10);//strncpy(ptr, "abc", 3);return 0;
}
2.訪問只讀的內存地址
錯誤做法:往字符串常量空間拷貝新的數據
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{char *ptr = "test";strcpy(ptr, "TEST1");return 0;
}
3.訪問系統保護的內存地址
如:
#include <stdio.h>int main(int argc, char *argv[])
{int *ptr = (int *)0;*ptr = 100;return 0;
}
4.棧溢出
如:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])
{main(argc, argv);
}
初學時兩種常用的段錯誤調試方法
1.使用printf輸出調試信息
這個是看似最簡單但往往很多情況下是十分有效的調試方式,也許可以說是程序員用的最多的調試方式。簡單來說,就是在程序的重要代碼附近加上printf輸出信息,這樣可以跟蹤并打印出段錯誤在代碼中最可能出現的位置。
使用案例:
以下面這段錯誤代碼為例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr沒有申請空間就直接使用return 0;
}
可以在代碼中添加printf調試信息:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{printf("line:%d\n", __LINE__); //單純打印代碼行數char *ptr = NULL;//This is the wrong implementation:printf("line:%d\n", __LINE__); //單純打印代碼行數strncpy(ptr, "abc", 3);//ptr沒有申請空間就直接使用printf("line:%d\n", __LINE__); //單純打印代碼行數return 0;
}
編譯運行后,會有如下輸出:
line:7
line:10
Segmentation fault (core dumped)
通過日志信息,就可以看到strncpy(ptr, “abc”, 3);語句沒有被執行,我們就可以根據定位到的位置來排除ptr是否是未分配空間或者分配的空間不足引發的問題
2.使用gcc和gdb
調試步驟:
1、為了能夠使用gdb調試程序,在編譯階段加上-g參數,還是以下面這段錯誤代碼為例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>int main(int argc, char *argv[])
{char *ptr = NULL;//This is the wrong implementation:strncpy(ptr, "abc", 3);//ptr沒有申請空間就直接使用return 0;
}
編譯代碼添加-g選項
gcc -g -o test test.c
編譯出test程序,然后用gdb調試:
linux@linux:~/test$ gdb ./test
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./test...done.
(gdb) r
Starting program: /home/linux/test/test Program received signal SIGSEGV, Segmentation fault.
0x0000555555554611 in main (argc=1, argv=0x7fffffffe528) at test.c:9
9 strncpy(ptr, "abc", 3);//ptr沒有申請空間就直接使用
(gdb) quit
A debugging session is active.Inferior 1 [process 15829] will be killed.Quit anyway? (y or n) y
從輸出看出,程序到SIGSEGV信號,觸發段錯誤,并提示地址0x0000555555554611以及代碼第9行出錯。
當然,還有利用core文件和gdb配置配合調試、使用objdump以及catchsegv命令的其他更為復雜的調試手段。