【C 陷阱與缺陷 學(xué)習(xí)筆記】(一)詞法陷阱

碼字不易,對(duì)你有幫助 點(diǎn)贊/轉(zhuǎn)發(fā)/關(guān)注 支持一下作者

微信搜公眾號(hào):不會(huì)編程的程序圓

看更多干貨,獲取第一時(shí)間更新

代碼,練習(xí)上傳至:https://github.com/hairrrrr/C-CrashCourse

一 內(nèi)容

0. =不同于==

當(dāng)程序員本意是作比較運(yùn)算時(shí),卻可能無(wú)意中誤寫(xiě)成了賦值運(yùn)算。

1.本意是檢查 x 與 y 是否相等:

if(x = y)
    break;

實(shí)際上是將 y 的值賦值給了 x ,然后再檢查該值是否為 0 。

2.本意是跳過(guò)文件中的空白字符:

while(c = '' || c == '\t' || c == '\n')
    c = getc(f);

因?yàn)?' '不等于 0 (' '的 ASCII 碼值為 32),那么無(wú)論變量為何值,上述表達(dá)式求值的結(jié)果都為 1,因此循環(huán)將進(jìn)行下去直到整個(gè)文件結(jié)束。

C 編譯器發(fā)現(xiàn)形如 x = y 的表達(dá)式出現(xiàn)在選擇語(yǔ)句,循環(huán)語(yǔ)句的條件判斷部分時(shí),會(huì)給出警告。當(dāng)確實(shí)需要對(duì)變量進(jìn)行賦值時(shí),為了避免警告,我們應(yīng)該這樣處理:

if((x = y) != 0)
    foo();

如果將賦值寫(xiě)成了比較,也會(huì)造成混淆:

if((filedesc == open(argv[i], 0)) < 0)
    error();

本例中,open 執(zhí)行成功返回非零值,失敗返回 -1。本意是將 open 函數(shù)的返回值存儲(chǔ)在變量 filedesc 中,然后將其和 0 比較大小,判斷 open 執(zhí)行是否成功 。==運(yùn)算符的結(jié)果只可能是 1 或 0,永遠(yuǎn)不會(huì)小于 0,所以 error() 將沒(méi)有機(jī)會(huì)被調(diào)用。

1. &|不同于&&||

比較 i & ji && j ,只要 i 和 j 是 0 或 1 ,兩個(gè)表達(dá)式的值是一樣的(||| 同理。)。然而,一旦 i 和 j 的值為其他,兩個(gè)表達(dá)式的值不會(huì)始終一致。

另一個(gè)區(qū)別是操作數(shù)帶有自增自減的運(yùn)算:

i & j++, j 始終會(huì)自增;但是 i && j++ 有時(shí) j 不會(huì)自增。

2. 詞法分析中的“貪心法”

當(dāng) C 的編譯器讀入一個(gè)字符/后跟著一個(gè)字符*時(shí),那么編譯器就必須做出判斷:時(shí)將其作為兩個(gè)符號(hào)對(duì)待,還是合起來(lái)作為一個(gè)符號(hào)對(duì)待。這類(lèi)問(wèn)題的規(guī)則:每個(gè)符號(hào)應(yīng)該包含盡可能多的符號(hào)

例如:a---b(a--) - b含義相同,而與a - (--b)含義不同。

又如:下面的語(yǔ)句本意是 x 除以 p 指向的值然后將結(jié)果賦值給 y

y = x/*p;

但是,實(shí)際上 /*被編譯器理解為一段注釋的開(kāi)始。

將上面的語(yǔ)句重寫(xiě)如下:

y = x / *p;

或者:

y = x/(*p);

老版本的編譯器允許使用=+來(lái)代表現(xiàn)在+=的含義,這種編譯器會(huì)將:

a=-1;

理解為:

a =- 1;

即為:

a = a - 1;

因此,如果程序員的原意為:

a = -1;

那么結(jié)果會(huì)讓其大吃一驚。

再如:

a=/*b;

在老版本的編譯器會(huì)將其當(dāng)作:

a =/ *b;

3. 整型常量

許多編譯器會(huì)把 8 和 9 作為把八進(jìn)制的數(shù)字處理,這種處理方式來(lái)源于八進(jìn)制數(shù)的定義。例如:0195 的含義是1x8^2 + 9x8 + 5x8^0也就是 141(十進(jìn)制)或 0215(八進(jìn)制)。ANSI C 標(biāo)準(zhǔn)中禁止這種用法。

4. 字符與字符串

單引號(hào)引起的一個(gè)字符實(shí)際上代表一個(gè)整數(shù)。整數(shù)值對(duì)應(yīng)于該字符在編譯器采用的字符集中的序列值。因此,對(duì)于采用 ASCII 字符集的編譯器而言,'a'的含義與 97 (十進(jìn)制)嚴(yán)格一致。

用雙引號(hào)引起的字符串,代表的確實(shí)一個(gè)指向無(wú)名數(shù)組起始字符的指針。該數(shù)組被雙引號(hào)之間的字符以及一個(gè)額外的二進(jìn)制值為 0 的字符\0初始化。

比如,下面的這個(gè)語(yǔ)句:

printf("Hello World\n");

等價(jià)于:

char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0};
printf(hello);

整數(shù)型(一般為 16 或 32 位)的存儲(chǔ)空間可以容納多個(gè)字符(一般為 8 位),因此有的編譯器允許在一個(gè)字符常量(以及字符串常量)中包含多個(gè)字符。也就是說(shuō):用'yes'代替"yes"不會(huì)被該編譯器檢測(cè)到。前者的含義大多數(shù)編譯器理解為一個(gè)整數(shù)值,由'y','e','s'所代表的整數(shù)值按照特定編譯器實(shí)現(xiàn)中的定義方式組合得到。

二 練習(xí)

練習(xí) 1

某些 C 編譯器允許嵌套注釋。請(qǐng)寫(xiě)一個(gè)測(cè)試程序,要求:無(wú)論編譯器是否允許嵌套注釋?zhuān)摮绦蚨寄苷Mㄟ^(guò)編譯,但是兩種情況下程序執(zhí)行結(jié)果不同。

對(duì)于符號(hào)序列:

/*/**/"*/"

如果允許嵌套注釋?zhuān)厦娴姆?hào)序列表示:一個(gè)單獨(dú)的雙引號(hào)",因?yàn)樽詈蟮淖⑨尫俺霈F(xiàn)的符號(hào)都會(huì)被當(dāng)作注釋的一部分。

如果不允許嵌套注釋?zhuān)厦娴姆?hào)就表示一個(gè)字符串:"*/"

Doug Mcllroy 發(fā)現(xiàn)了下面這個(gè)令人拍案叫絕的解法:

/*/*/0 */**/1

這個(gè)解法主要利用了編譯器作詞發(fā)分析時(shí)的“貪心法”規(guī)則。

如果編譯器允許嵌套注釋?zhuān)瑒t將上式解釋為:

/* /*/0 */ * */ 1

上式的值為 1

如果編譯器不允許嵌套注釋?zhuān)瑒t解釋為:

/* / */ 0 * /**/ 1

也就是 0*1,值為 0

練習(xí) 2

a+++++b 的含義是什么?

上式唯一有意義的解析方式就是:

a++ + ++b

可是,根據(jù)“貪心法”的規(guī)則,上式應(yīng)該被解釋為:

a++ ++ + b

等價(jià)于:

(a++)++ + b;

但是 a++的值不能作為左值,因此編譯器不會(huì)接受 a++ 作為后面 ++ 運(yùn)算的操作數(shù)。

參考資料《C 缺陷與陷阱》


以上就是本次的內(nèi)容,感謝觀看。

如果文章有錯(cuò)誤歡迎指正和補(bǔ)充,感謝!

最后,如果你還有什么問(wèn)題或者想知道到的,可以在評(píng)論區(qū)告訴我呦,我在后面的文章可以加上。

最后,關(guān)注我,看更多干貨!

我是程序圓,我們下次再見(jiàn)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容