碼字不易,對(duì)你有幫助 點(diǎn)贊/轉(zhuǎn)發(fā)/關(guān)注 支持一下作者
微信搜公眾號(hào):不會(huì)編程的程序圓
看更多干貨,獲取第一時(shí)間更新
推薦閱讀原文:
https://mp.weixin.qq.com/s/c5jot1YJcyeIniFkTVdNag
請(qǐng)看下面的程序,它用來(lái)進(jìn)行復(fù)制文件的操作,你覺(jué)得它有問(wèn)題嗎:
#include<stdio.h>
#include<stdlib.h>
int main(int argc, char* argv[]) {
FILE* src_fp, * dest_fp;
int ch;
if (argc != 3) {
fprintf(stderr, "usage: fcopy source dest\n");
exit(EXIT_FAILURE);
}
if ((src_fp = fopen(argv[1], "rb") == NULL)) {
fprintf(stderr, "Can't open file %s\n", argv[1]);
exit(EXIT_FAILURE);
}
if ((dest_fp = fopen(argv[2], "wb")) == NULL) {
fprintf(stderr, "Can't open file %s\n", argv[2]);
fclose(src_fp);
exit(EXIT_FAILURE);
}
while ((ch = getc(src_fp)) != EOF)
putc(ch, dest_fp);
fclose(src_fp);
fclose(dest_fp);
return 0;
}
程序可以正常編譯,但是當(dāng)我們?cè)诿钚兄休斎胝_的命令(命令格式:可執(zhí)行程序名 文件1 文件2)想要復(fù)制文件時(shí),會(huì)出現(xiàn)一個(gè) assertion :
遇到這種問(wèn)題我們不要慌張。我們先閱讀一下這個(gè)錯(cuò)誤提示,看能否找到可以幫助自己 debug 的有用信息。
第一次看這個(gè)錯(cuò)誤提示我是摸不著頭腦的。我仔細(xì)的檢查了自己的程序,在確定“沒(méi)有問(wèn)題”后,我又仔細(xì)的看了看這個(gè)錯(cuò)誤提示。請(qǐng)大家注意 fgetc.cpp 這里,這說(shuō)明出錯(cuò)的地方有可能是 fgetc 函數(shù)內(nèi)部。
注意接下來(lái)的一行:Expression: stream.valid();
到目前為止,我們知道可能是是程序執(zhí)行到 fgetc 函數(shù)內(nèi)部中 stream.valid() 這一行時(shí)產(chǎn)生了錯(cuò)誤。那么問(wèn)題到底是什么呢?
第一種辦法是 google 一下,看看有沒(méi)有類似的問(wèn)題解答;如果沒(méi)有,那么你就要靠自己了。
我們思考一下,表達(dá)式 stream.valid() 什么情況下會(huì)出錯(cuò)?有人可能要說(shuō)了,我連這個(gè)表達(dá)式的意思是什么都不知道,我怎么知道什么情況下會(huì)出錯(cuò)呢?其實(shí)我也不知道,但這不影響我們思考出錯(cuò)的可能性。
最直觀的一種可能就是 stream 是空指針時(shí),對(duì)其進(jìn)行成員訪問(wèn)?,F(xiàn)在我們需要做的就是找到自己程序中哪里可能傳入了空指針。
這也很簡(jiǎn)單,程序中我們只有一個(gè)函數(shù)和 fgetc 有關(guān) —— getc 。但為什么我們用的是 getc 但是出錯(cuò)的是 fgetc 呢?因?yàn)?getc 函數(shù)的實(shí)現(xiàn)依賴于 fgetc 函數(shù)。
我們來(lái)看一下我們對(duì) getc 的調(diào)用:
getc(src_fp)
這時(shí)你可能又要說(shuō)了,src_fp 我們不是做過(guò)空指針檢查嗎,為什么還會(huì)出錯(cuò)?不要著急著下結(jié)論,我們來(lái)看看這個(gè)檢查 src_fp 的語(yǔ)句:
if ((src_fp = fopen(argv[1], "rb") == NULL)) {
...
}
其實(shí)到這一步,明眼人都能看出問(wèn)題的所在了。
當(dāng)我確定了 src_fp 可能為空指針時(shí),又看到了 if 語(yǔ)句中的 = 和 == ,我已經(jīng)明白錯(cuò)誤所在了。讀過(guò)《C 陷阱和缺陷》的朋友都清楚,這是一個(gè)經(jīng)典的由優(yōu)先級(jí)引發(fā)的錯(cuò)誤:
因?yàn)?C 語(yǔ)言中,賦值運(yùn)算的優(yōu)先級(jí)往往是比大多數(shù)其他運(yùn)算符都要低的(包括關(guān)系運(yùn)算符 == 和 != ),if 語(yǔ)句的判斷會(huì)被編譯器理解為:
if ( (src_fp = (fopen(argv[1], "rb") == NULL) ) {
...
}
我們知道:fopen(argv[1], "rb") == NULL的值的可能只有兩個(gè):0 或 1,不管我們將哪個(gè)賦值給 src_fp ,都會(huì)引發(fā)問(wèn)題。
雖然最終的錯(cuò)誤嚴(yán)謹(jǐn)?shù)恼f(shuō)并不能說(shuō)是空指針異常,但是我們發(fā)現(xiàn)了形參 stream 是有問(wèn)題的,也就說(shuō)明我們傳入的實(shí)參是不正確的。
引發(fā)這個(gè)問(wèn)題的原因是這樣的:在我寫這個(gè) if 語(yǔ)句的判斷表達(dá)式時(shí),我在 if 后打了一次左括號(hào),VS 為我補(bǔ)了一個(gè)右括號(hào)(這樣就完整的錄入了())。然后在括號(hào)內(nèi)我先寫了 src_fp = fopen(argv[1], "rb")。在要寫出判等語(yǔ)句前,我認(rèn)為應(yīng)該為前面的賦值語(yǔ)句添加括號(hào),所以我將光標(biāo)移動(dòng)到了 src_fp 前,打出了左括號(hào)(,然后又我將光標(biāo)移動(dòng)到 fopen 函數(shù)的調(diào)用后,敲了一次左括號(hào),很可惜,VS 就是這么坑爹,由于右邊已經(jīng)有了一個(gè)右括號(hào)(這是屬于整體的),我白敲了一次右括號(hào),并沒(méi)有錄入這個(gè)右括號(hào)。后面在寫出判等代碼后(if ((src_fp = fopen(argv[1], "rb") == NULL)),VS 其實(shí)是有報(bào)錯(cuò)的,但我自以為是的只是簡(jiǎn)單的在后面添加了一個(gè)右括號(hào)而已。從而造成了這個(gè) bug 。
因?yàn)橐粋€(gè)不經(jīng)大腦的右括號(hào),我花了很久 debug ,又“浪費(fèi)”時(shí)間寫了一篇博客!這個(gè)故事告訴我們,下次在你添加右括號(hào)時(shí),一定要停下了思考一下,以避免寫一篇“不必要”的博客。
雖然這個(gè) bug 其實(shí)并不難找,但是我依然明白了,即使面對(duì)可能再難的 bug,稍加思考,它可能就會(huì)變成“紙老虎”。
或者,讓你自己不要寫出這樣的 bug 。