【C 陷阱與缺陷 】(一)詞法陷阱

碼字不易,對你有幫助 點贊/轉發/關注 支持一下作者

微信搜公衆號:不會編程的程序圓

看更多幹貨,獲取第一時間更新

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

一 內容

0. =不同於==

當程序員本意是作比較運算時,卻可能無意中誤寫成了賦值運算。

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

if(x = y)
    break;

實際上是將 y 的值賦值給了 x ,然後再檢查該值是否爲 0 。

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

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

因爲 ' '不等於 0 (' '的 ASCII 碼值爲 32),那麼無論變量爲何值,上述表達式求值的結果都爲 1,因此循環將進行下去直到整個文件結束。

C 編譯器發現形如 x = y 的表達式出現在選擇語句,循環語句的條件判斷部分時,會給出警告。當確實需要對變量進行賦值時,爲了避免警告,我們應該這樣處理:

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

如果將賦值寫成了比較,也會造成混淆:

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

本例中,open 執行成功返回非零值,失敗返回 -1。本意是將 open 函數的返回值存儲在變量 filedesc 中,然後將其和 0 比較大小,判斷 open 執行是否成功 。==運算符的結果只可能是 1 或 0,永遠不會小於 0,所以 error() 將沒有機會被調用。

1. &|不同於&&||

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

另一個區別是操作數帶有自增自減的運算:

i & j++, j 始終會自增;但是 i && j++ 有時 j 不會自增。

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

當 C 的編譯器讀入一個字符/後跟着一個字符*時,那麼編譯器就必須做出判斷:時將其作爲兩個符號對待,還是合起來作爲一個符號對待。這類問題的規則:每個符號應該包含儘可能多的符號

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

又如:下面的語句本意是 x 除以 p 指向的值然後將結果賦值給 y

y = x/*p;

但是,實際上 /*被編譯器理解爲一段註釋的開始。

將上面的語句重寫如下:

y = x / *p;

或者:

y = x/(*p);

老版本的編譯器允許使用=+來代表現在+=的含義,這種編譯器會將:

a=-1;

理解爲:

a =- 1;

即爲:

a = a - 1;

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

a = -1;

那麼結果會讓其大喫一驚。

再如:

a=/*b;

在老版本的編譯器會將其當作:

a =/ *b;

3. 整型常量

許多編譯器會把 8 和 9 作爲把八進制的數字處理,這種處理方式來源於八進制數的定義。例如:0195 的含義是1x8^2 + 9x8 + 5x8^0也就是 141(十進制)或 0215(八進制)。ANSI C 標準中禁止這種用法。

4. 字符與字符串

單引號引起的一個字符實際上代表一個整數。整數值對應於該字符在編譯器採用的字符集中的序列值。因此,對於採用 ASCII 字符集的編譯器而言,'a'的含義與 97 (十進制)嚴格一致。

用雙引號引起的字符串,代表的確實一個指向無名數組起始字符的指針。該數組被雙引號之間的字符以及一個額外的二進制值爲 0 的字符\0初始化。

比如,下面的這個語句:

printf("Hello World\n");

等價於:

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

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

二 練習

練習 1

某些 C 編譯器允許嵌套註釋。請寫一個測試程序,要求:無論編譯器是否允許嵌套註釋,該程序都能正常通過編譯,但是兩種情況下程序執行結果不同。

對於符號序列:

/*/**/"*/"

如果允許嵌套註釋,上面的符號序列表示:一個單獨的雙引號",因爲最後的註釋符前出現的符號都會被當作註釋的一部分。

如果不允許嵌套註釋,上面的符號就表示一個字符串:"*/"

Doug Mcllroy 發現了下面這個令人拍案叫絕的解法:

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

這個解法主要利用了編譯器作詞發分析時的“貪心法”規則。

如果編譯器允許嵌套註釋,則將上式解釋爲:

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

上式的值爲 1

如果編譯器不允許嵌套註釋,則解釋爲:

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

也就是 0*1,值爲 0

練習 2

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

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

a++ + ++b

可是,根據“貪心法”的規則,上式應該被解釋爲:

a++ ++ + b

等價於:

(a++)++ + b;

但是 a++的值不能作爲左值,因此編譯器不會接受 a++ 作爲後面 ++ 運算的操作數。

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


以上就是本次的內容,感謝觀看。

如果文章有錯誤歡迎指正和補充,感謝!

最後,如果你還有什麼問題或者想知道到的,可以在評論區告訴我呦,我在後面的文章可以加上。

最後,關注我,看更多幹貨!

我是程序圓,我們下次再見。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章