在 Linux下編譯及調(diào)試 C 代碼的簡(jiǎn)易指南
對(duì)于Linux下的C程序員來 說,幾乎天天都會(huì)和Linux打交道。但在很多人的眼中,Linux是一個(gè)易用性極差、靠命令驅(qū)動(dòng)的操作系統(tǒng),根本無法與有著友好用戶界面的 Windows相比。確實(shí)是這樣的,即使大家的程序是運(yùn)行在Linux下,基于以下種種原因,我們的大部分工作還是在Windows下完成的:
第一,除了編譯調(diào)試代碼之外,每個(gè)程序員還有很多工作要做,像文檔編寫、郵件發(fā)送及回復(fù)、PPT制作等,這些工作在Windows下做要更方便快捷一些。
第二,公司及項(xiàng)目組的資源有限,一般不會(huì)為每個(gè)開發(fā)人員配備一臺(tái)安裝有Linux的機(jī)器,而是大家共用一臺(tái)或少許幾臺(tái)Linux機(jī)器。在每臺(tái)機(jī)器上建立多個(gè)用戶,需要用來編譯或調(diào)試程序的時(shí)候,大家用某個(gè)用戶登錄上去。
這樣,問題就來了:自己平時(shí)是在Windows下面辦公的,而自己編寫的程序的運(yùn)行環(huán)境又是Linux的,如何從Windows切換到Linux呢?是不是要到專門的Linux機(jī)器上去編寫代碼呢?我們?nèi)绾卧贚inux下調(diào)試程序呢?本文將一一道來。
到Linux下去編譯運(yùn)行程序的步驟
只要在Windows下安裝一個(gè)叫做SecureCRT的軟件和一個(gè)叫做FileZilla的軟件,便可輕松實(shí)現(xiàn)Windows到Linux的切換。
SecureCRT是一款支持SSH(SSH1和SSH2)的終端仿真程序,簡(jiǎn)單地說是Windows下登錄Linux服務(wù)器主機(jī)的軟件。 FileZilla是一個(gè)免費(fèi)開源的FTP軟件,分為客戶端版本和服務(wù)器版本兩種,具備所有的FTP軟件功能。(編者注:SecureCRT 是版權(quán)軟件,建議使用開源的 Putty 替代,不過切記勿在搜索引擎隨便搜索下載。)
在使用SecureCRT和FileZilla之前,要確保有一臺(tái)安裝了Linux的機(jī)器處于運(yùn)行狀態(tài)(一般說來,每個(gè)開發(fā)小組都會(huì)有專門用于測(cè)試 程序的機(jī)器,可以在此機(jī)器上安裝Linux)。作者使用的Linux機(jī)器的IP地址為xx.xx.xx.xx,用戶名為zxin10,密碼為yyyy。
第一步:使用SecureCRT登錄Linux
打開SecureCRT軟件,在界面上輸入IP和用戶名,如圖1所示。
圖1 登錄界面
然后,單擊圖1中的“Connect”,在出現(xiàn)的界面上輸入密碼,如圖2所示。
圖2 密碼輸入界面
密碼輸入正確之后,便登錄到了Linux系統(tǒng)下,如圖3所示。
圖3 登錄成功之后的界面
為了編譯自己的程序,我們需要建立自己的文件存放目錄,如圖4所示。
圖4 新建個(gè)人目錄
目錄建立成功之后,我們便可以轉(zhuǎn)到目錄中去看一下,如圖5所示。
圖5 轉(zhuǎn)到新建目錄
此時(shí),“萬事俱備,只欠東風(fēng)”,我們接下來要做的工作是利用FileZilla軟件將自己在Windows下編寫的程序傳上去。
示例程序如下:
- /**********************************************************************
- * 版權(quán)所有 (C)2015, Zhou Zhaoxiong。
- *
- * 文件名稱:Hello.c
- * 文件標(biāo)識(shí):無
- * 內(nèi)容摘要:演示W(wǎng)indows下編寫的程序如何在Linux下執(zhí)行
- * 其它說明:無
- * 當(dāng)前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:201501028
- *
- **********************************************************************/
- #include <stdio.h>
- /**********************************************************************
- * 功能描述:主函數(shù)
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返 回 值:0-執(zhí)行完畢
- * 其它說明:無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * -------------------------------------------------------------------
- * 201501028 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ***********************************************************************/
- int main()
- {
- printf("Hello, world!/n");
- return 0;
- }
第二步:使用FileZilla將代碼上傳到Linux
將該“Hello.c”文件存放在D盤的“Test”文件夾下,并啟動(dòng)FileZilla,如圖6所示。
圖6 啟動(dòng)FileZilla之后的界面
在“主機(jī)(H)”中輸入IP地址,在“用戶名(U)”中輸入“zxin10”用戶名,在“密碼(W)”中輸入正確的密碼,“端口(P)”可不填寫而 使用默認(rèn)值,則可登錄到Linux機(jī)器上去。登上去后,轉(zhuǎn)到“zhouzx”目錄下,并將“Hello.c”文件傳上去,如圖7所示。
圖7 上傳文件之后的界面
此時(shí),“Hello.c”文件已經(jīng)傳到了“zhouzx”目錄下,現(xiàn)在可以對(duì)該文件進(jìn)行編譯了。
第三步:在Linux上編譯和運(yùn)行程序。
使用“gcc -g -o Hello Hello.c”命令對(duì)文件進(jìn)行編譯,如圖8所示。
圖8 編譯之后的結(jié)果
可以看到,編譯成功之后,有“Hello”文件生成。緊接著,運(yùn)行“Hello”命令,便可看到程序的輸出結(jié)果,如圖9所示。
圖9 程序的輸出結(jié)果
以上便是將Windows下的程序放到Linux下去編譯和運(yùn)行的全過程。這里只是示例了簡(jiǎn)單的程序,實(shí)際軟件開發(fā)項(xiàng)目中的程序要復(fù)雜很多,但基本 操作流程都是類似的。當(dāng)然,直接在Linux下編寫程序也是可以的,如可以利用VI編輯器來寫程序。但由于易用性的原因,我認(rèn)為,在Windows下編寫 程序要更方便一點(diǎn)。大家要根據(jù)自己的習(xí)慣及項(xiàng)目組的要求來選擇合理的代碼編寫的方式。
程序調(diào)試示例—用gdb分析core文件
在實(shí)際的軟件開發(fā)項(xiàng)目中,程序出現(xiàn)問題是在所難免的。遙想本人參加工作之后首次遇到程序的情景,至今還歷歷在目。之前的經(jīng)驗(yàn)告訴我,我們?cè)绞求@慌失措,問題就越是解決不了。我們要先讓自己平靜下來,然后再尋找解決程序問題的辦法。
在Linux下做開發(fā)的朋友,想必都與core文件打過交道。當(dāng)看到自己的程序運(yùn)行之后出現(xiàn)core時(shí),很多人都慌亂了,仿佛天快要塌下來一樣。其 實(shí),我們大可不必如此,只要我們掌握了用gdb調(diào)試core文件的辦法,依然可以很快定位程序問題,一舉將bug消滅掉。有關(guān)Linux core文件的更多介紹,請(qǐng)閱讀此文。
這里以一個(gè)實(shí)際的程序?yàn)槔?,以用gdb分析core文件為例介紹了Linux下程序調(diào)試的方法,同時(shí)演示了常見gdb命令的操作方法。
在Linux下執(zhí)行“ulimit –a”命令查看程序運(yùn)行出錯(cuò)時(shí)是否會(huì)產(chǎn)生core文件,命令執(zhí)行的結(jié)果中有“core file size = 0”表示不會(huì)產(chǎn)生core文件,此時(shí)要使用“ulimit -c 1000000”命令設(shè)置core文件的大小。
示例程序
- /**********************************************************************
- * 版權(quán)所有 (C)2015, Zhou Zhaoxiong。
- *
- * 文件名稱:GdbDebug.c
- * 文件標(biāo)識(shí):無
- * 內(nèi)容摘要:Gdb命令演示程序
- * 其它說明:無
- * 當(dāng)前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:20151008
- *
- **********************************************************************/
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- // 數(shù)據(jù)類型重定義
- typedef unsigned char UINT8;
- typedef signed int INT32;
- typedef unsigned int UINT32;
- // 函數(shù)聲明
- void Sleep(UINT32 iCountMs);
- void PrintInfo(void);
- INT32 main();
- /**********************************************************************
- * 功能描述:主函數(shù)
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返 回 值:無
- * 其它說明:無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * -------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ***********************************************************************/
- INT32 main()
- {
- PrintInfo(); // 在屏幕上輸出消息
- return 0;
- }
- /**********************************************************************
- * 功能描述: 在屏幕上輸出消息
- * 輸入?yún)?shù): 無
- * 輸出參數(shù): 無
- * 返 回 值: 無
- * 其它說明: 無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * ----------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ************************************************************************/
- void PrintInfo(void)
- {
- UINT32 iLoopFlag = 0;
- UINT32 iSum = 0;
- UINT32 iLen = 0;
- UINT8 *pCtrStr = NULL;
- iLen = strlen(pCtrStr);
- for (iLoopFlag = 0; iLoopFlag < iLen; iLoopFlag ++) // 打印消息iLen次
- {
- printf("PrintInfo: hello, world!/n");
- iSum = iSum + iLoopFlag;
- Sleep(10 * 1000); // 每10s打印一次
- }
- return;
- }
- /**********************************************************************
- * 功能描述: 程序休眠
- * 輸入?yún)?shù): iCountMs-休眠時(shí)間(單位:ms)
- * 輸出參數(shù): 無
- * 返 回 值: 無
- * 其它說明: 無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * ------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- void Sleep(UINT32 iCountMs)
- {
- struct timeval t_timeout = {0};
- if (iCountMs < 1000)
- {
- t_timeout.tv_sec = 0;
- t_timeout.tv_usec = iCountMs * 1000;
- }
- else
- {
- t_timeout.tv_sec = iCountMs / 1000;
- t_timeout.tv_usec = (iCountMs % 1000) * 1000;
- }
- select(0, NULL, NULL, NULL, &t_timeout); // 調(diào)用select函數(shù)阻塞程序
- }
用gdb分析core文件
在Linux上用“gcc -g -o GdbDebug GdbDebug.c”命令對(duì)程序進(jìn)行編譯之后,運(yùn)行“GdbDebug”命令,發(fā)現(xiàn)在當(dāng)前目錄下出現(xiàn)了core文件。利用gdb命令對(duì)core文件進(jìn)行分析的過程如下所示:
- ~/zhouzhaoxiong/zzx/GdbDebug> gdb GdbDebug core -- 啟動(dòng)gdb對(duì)core文件的分析
- GNU gdb (GDB) SUSE (7.3-0.6.1)
- Copyright (C) 2011 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-suse-linux".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/zhou/zhouzhaoxiong/zzx/GdbDebug/GdbDebug...done.
- Core was generated by `GdbDebug'.
- Program terminated with signal 11, Segmentation fault.
- #0 0x00007f4a736f9812 in __strlen_sse2 () from /lib64/libc.so.6
- (gdb) where -- 查看程序出問題的地方
- #0 0x00007f4a736f9812 in __strlen_sse2 () from /lib64/libc.so.6
- #1 0x000000000040061a in PrintInfo () at GdbDebug.c:64 -- 可以看到,在GdbDebug.c文件的第64行出的問題
- #2 0x00000000004005e5 in main () at GdbDebug.c:41
- (gdb) b 41 -- 在GdbDebug.c文件第41行設(shè)立斷點(diǎn)
- Breakpoint 1 at 0x4005e0: file GdbDebug.c, line 41.
- (gdb) b 64 -- 在GdbDebug.c文件第64行設(shè)立斷點(diǎn)
- Breakpoint 2 at 0x400611: file GdbDebug.c, line 64.
- (gdb) info b -- 顯示斷點(diǎn)信息
- Num Type Disp Enb Address What
- 1 breakpoint keep y 0x00000000004005e0 in main at GdbDebug.c:41
- 2 breakpoint keep y 0x0000000000400611 in PrintInfo at GdbDebug.c:64
- (gdb) r -- 運(yùn)行GdbDebug
- Starting program: /home/zhou/zhouzhaoxiong/zzx/GdbDebug/GdbDebug
- Breakpoint 1, main () at GdbDebug.c:41
- 41 PrintInfo(); // 在屏幕上輸出消息
- (gdb) n -- 執(zhí)行下一步
- Breakpoint 2, PrintInfo () at GdbDebug.c:64
- 64 iLen = strlen(pCtrStr);
- (gdb) p iLen -- 打印(輸出)iLen的值
- $1 = 0
- (gdb) p iLoopFlag -- 打印(輸出)iLoopFlag的值
- $2 = 0
- (gdb) c -- 繼續(xù)執(zhí)行
- Continuing.
- Program received signal SIGSEGV, Segmentation fault. -- 程序core掉了
- 0x00007ffff7ae9812 in __strlen_sse2 () from /lib64/libc.so.6
- (gdb) q -- 退出gdb
- A debugging session is active.
- Inferior 1 [process 26640] will be killed.
- Quit anyway? (y or n) y
- ~/zhouzhaoxiong/zzx/GdbDebug>
從以上分析可知,執(zhí)行GdbDebug.c文件的第64行時(shí)程序core掉了。此時(shí)仔細(xì)分析程序,發(fā)現(xiàn)pCtrStr指針為空。當(dāng)對(duì)一個(gè)不存在的指針取長(zhǎng)度時(shí),由于找不到地址,程序便崩潰了。修改的辦法也非常的簡(jiǎn)單,只需要讓pCtrStr指針指向具體的地址即可。
常見gdb命令操作示例
修改之后的代碼如下:
- /**********************************************************************
- * 版權(quán)所有 (C)2015, Zhou Zhaoxiong。
- *
- * 文件名稱:GdbDebug.c
- * 文件標(biāo)識(shí):無
- * 內(nèi)容摘要:Gdb命令演示程序
- * 其它說明:無
- * 當(dāng)前版本:V1.0
- * 作 者:Zhou Zhaoxiong
- * 完成日期:20151008
- *
- **********************************************************************/
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- // 數(shù)據(jù)類型重定義
- typedef unsigned char UINT8;
- typedef signed int INT32;
- typedef unsigned int UINT32;
- // 函數(shù)聲明
- void Sleep(UINT32 iCountMs);
- void PrintInfo(void);
- INT32 main();
- /**********************************************************************
- * 功能描述:主函數(shù)
- * 輸入?yún)?shù):無
- * 輸出參數(shù):無
- * 返 回 值:無
- * 其它說明:無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * -------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ***********************************************************************/
- INT32 main()
- {
- PrintInfo(); // 在屏幕上輸出消息
- return 0;
- }
- /**********************************************************************
- * 功能描述: 在屏幕上輸出消息
- * 輸入?yún)?shù): 無
- * 輸出參數(shù): 無
- * 返 回 值: 無
- * 其它說明: 無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * ----------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ************************************************************************/
- void PrintInfo(void)
- {
- UINT32 iLoopFlag = 0;
- UINT32 iSum = 0;
- UINT32 iLen = 0;
- UINT8 *pCtrStr = "hello, world!"; // 修改了這行代碼
- iLen = strlen(pCtrStr);
- for (iLoopFlag = 0; iLoopFlag < iLen; iLoopFlag ++) // 打印消息iLen次
- {
- printf("PrintInfo: hello, world!/n");
- iSum = iSum + iLoopFlag;
- Sleep(10 * 1000); // 每10s打印一次
- }
- return;
- }
- /**********************************************************************
- * 功能描述: 程序休眠
- * 輸入?yún)?shù): iCountMs-休眠時(shí)間(單位:ms)
- * 輸出參數(shù): 無
- * 返 回 值: 無
- * 其它說明: 無
- * 修改日期 版本號(hào) 修改人 修改內(nèi)容
- * ------------------------------------------------------------------
- * 20151008 V1.0 Zhou Zhaoxiong 創(chuàng)建
- ********************************************************************/
- void Sleep(UINT32 iCountMs)
- {
- struct timeval t_timeout = {0};
- if (iCountMs < 1000)
- {
- t_timeout.tv_sec = 0;
- t_timeout.tv_usec = iCountMs * 1000;
- }
- else
- {
- t_timeout.tv_sec = iCountMs / 1000;
- t_timeout.tv_usec = (iCountMs % 1000) * 1000;
- }
- select(0, NULL, NULL, NULL, &t_timeout); // 調(diào)用select函數(shù)阻塞程序
- }
編譯并運(yùn)行之后,程序正常,說明問題已被我們解決掉。下面是常見的gdb命令的操作示例:
- ~/zhouzhaoxiong/zzx/GdbDebug> gdb GdbDebug -- 啟動(dòng)gdb調(diào)試
- GNU gdb (GDB) SUSE (7.3-0.6.1)
- Copyright (C) 2011 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-suse-linux".
- For bug reporting instructions, please see:
- <http://www.gnu.org/software/gdb/bugs/>...
- Reading symbols from /home/zhou/zhouzhaoxiong/zzx/GdbDebug/GdbDebug...done.
- (gdb) b 64 -- 在GdbDebug.c文件第64行設(shè)立斷點(diǎn)
- Breakpoint 1 at 0x400611: file GdbDebug.c, line 64.
- (gdb) b 72 -- 在GdbDebug.c文件第72行設(shè)立斷點(diǎn)
- Breakpoint 2 at 0x400637: file GdbDebug.c, line 72.
- (gdb) info b -- 顯示斷點(diǎn)信息
- Num Type Disp Enb Address What
- 1 breakpoint keep y 0x0000000000400611 in PrintInfo at GdbDebug.c:64
- 2 breakpoint keep y 0x0000000000400637 in PrintInfo at GdbDebug.c:72
- (gdb) r -- 運(yùn)行GdbDebug
- Starting program: /home/zhou/zhouzhaoxiong/zzx/GdbDebug/GdbDebug
- Breakpoint 1, PrintInfo () at GdbDebug.c:64
- 64 iLen = strlen(pCtrStr);
- (gdb) p iLen -- 打印(輸出)iLen的值
- $1 = 0
- (gdb) n -- 執(zhí)行下一步
- 66 for (iLoopFlag = 0; iLoopFlag < iLen; iLoopFlag ++) // 打印消息iLen次
- (gdb) n -- 執(zhí)行下一步
- 68 printf("PrintInfo: hello, world!/n");
- (gdb) p iLoopFlag -- 打印(輸出)iLoopFlag的值
- $2 = 0
- (gdb) p iLen -- 打印(輸出)iLen的值
- $3 = 13
- (gdb) n -- 執(zhí)行下一步
- PrintInfo: hello, world! -- 程序的輸出結(jié)果
- 70 iSum = iSum + iLoopFlag;
- (gdb) p iSum -- 打印(輸出)iSum的值
- $4 = 0
- (gdb) n -- 執(zhí)行下一步
- Breakpoint 2, PrintInfo () at GdbDebug.c:72
- 72 Sleep(10 * 1000); // 每10s打印一次
- (gdb) n
- 66 for (iLoopFlag = 0; iLoopFlag < iLen; iLoopFlag ++) // 打印消息iLen次
- (gdb) p iLoopFlag
- $5 = 0
- (gdb) n
- 68 printf("PrintInfo: hello, world!/n");
- (gdb) p iLoopFlag
- $6 = 1
- (gdb) n
- PrintInfo: hello, world!
- 70 iSum = iSum + iLoopFlag;
- (gdb) p iSum
- $7 = 0
- (gdb) n
- Breakpoint 2, PrintInfo () at GdbDebug.c:72
- 72 Sleep(10 * 1000); // 每10s打印一次
- (gdb) p iSum
- $8 = 1
- (gdb) finish -- 一直運(yùn)行到函數(shù)返回
- Run till exit from #0 PrintInfo () at GdbDebug.c:72
- PrintInfo: hello, world!
- Breakpoint 2, PrintInfo () at GdbDebug.c:72
- 72 Sleep(10 * 1000); // 每10s打印一次
- (gdb) c -- 繼續(xù)執(zhí)行
- Continuing.
- PrintInfo: hello, world!
- Breakpoint 2, PrintInfo () at GdbDebug.c:72
- 72 Sleep(10 * 1000); // 每10s打印一次
- (gdb) bt -- 打印當(dāng)前的函數(shù)調(diào)用棧的所有信息
- #0 PrintInfo () at GdbDebug.c:72
- #1 0x00000000004005e5 in main () at GdbDebug.c:41
- (gdb) q -- 退出gdb
- A debugging session is active.
- Inferior 1 [process 26685] will be killed.
- Quit anyway? (y or n) y
- ~/zhouzhaoxiong/zzx/GdbDebug>
作為L(zhǎng)inux下調(diào)試C/C++程序的工具,大家一定要熟練掌握gdb的用法。
總結(jié)
Linux具有免費(fèi)、可靠、安全、穩(wěn)定、多平臺(tái)等特點(diǎn),因此深受全球各大IT廠商的追捧。Linux操作系統(tǒng)的兩大主要應(yīng)用領(lǐng)域是服務(wù)器領(lǐng)域和嵌入式Linux系統(tǒng)。不管你從事的開發(fā)工作是否與Linux有關(guān),掌握Linux下的軟件開發(fā)方法總是有好處的。