【讀書筆記】C陷阱與缺陷
詞法陷阱
= 不同於 ==
&和|不同於&&和||
詞法分析的“貪心法”:從左到右一個字元一個字元地讀入,如果該字元能組成一個符號,那麼再讀入下一個字元,判斷這兩個字元組成的字串是否可能是一個符號的組成部分。(需要注意的是,除了字串和字元常量,符號間不能有空白)
e.g. a---b 等同於 a-- -b
y = x/*p,/*會被看成註釋
如果一個整形常量的第一個字元是數字0,會被看作八進位制
單引號括起的字元代表一個整數,雙引號括起的字元代表一個指標
語法陷阱
float *g(), (*h)(); g是一個函式,返回值是指向浮點數的指標,h是一個函式指標
運算子的優先順序:優先順序最高者其實並不是真正意義上的運算子,包括:陣列下標、函式呼叫操作符、結構成員選擇操作符。它們都是從左向右結合。單目運算子的優先順序次之(從右向左結合),接下來是雙目運算子(算術運算、移位運算子、關心運算負,接著是邏輯運算子、賦值運算子),最後是條件運算子
要記住的是
1. 任何一個邏輯運算子的優先順序低於任何一個關係運算符
2. 移位運算子的優先順序比算術運算子低,比關係運算符高
特別要注意作為語句結束標誌的分號!
switch和break的關係要處理好~
函式呼叫時即使函式不帶引數,也應該包括引數列表f()
else總是與自己最近的if結合
語義陷阱
空指標並非空字串,常數0被轉換為指標使用時,絕對不可以被解引用
只有四個運算子(&&,||,?:,和,)存在特定的求值順序
while(i < n)
y[i] = x[i++],i什麼時候++,不能保證
要考慮整數溢位的情況
連線
聯結器不懂C語言,但是理解機器語言和記憶體佈局,編譯器的責任就是把C源程式翻譯成對聯結器有意義的形式,聯結器把這些目標模組整合為載入模組或可執行檔案的實體。
extern關鍵字表明該變數的儲存空間是在程式的其他地方分配的
一個C程式有兩個原始檔,一個包含宣告extern int n,另一包含外部變數定義longn,這是無效的,同一外部變數在不同的檔案中被宣告為不同的型別
1. 典型的聯結器把由編譯器或者彙編器生成的若干個目標模組,整合成一個被稱為載入模組或可執行檔案的實體,該實體能夠被作業系統直接執行。
2. 每個外部變數只能定義一次。
3. 為了避免出現的命名衝突,如果一個函式僅僅被同一個原始檔中的其他函式呼叫,我們應該宣告該函式為static.
4. 函式應該在呼叫前定義或宣告,否則,返回值預設為整型。
5. 保證一個特定名稱的所有外部定義在每個目標模組中都有相同的型別。
6. 我們可以遵循一個簡單的規則:每個外部物件只在一個地方宣告,一般是在標頭檔案中。
庫函式
1. 返回整數的getchar函式
2. 輸入fread和輸出fwrite同時操作,需在其中插入fseek函式的呼叫。
3. 呼叫庫函式時,應該先檢查作為錯誤指示的返回值,確定程式執行失敗後,再檢查errno,來搞清楚出錯原因。
4. 訊號非常複雜,而且有一些不可移植的特性。讓signal處理函式儘可能簡單。
5. getchar在stdio.h中被實現為一個巨集,如果沒有include這個標頭檔案,getchar可能被作為一個函式,所以導致getchar比較慢。
前處理器
1. 預處理:常量替換;巨集替換(避免函式呼叫的開銷)
2. 巨集定義中要注意空格 #define f (x) {(x) - 1 },意為f(x)代表(x) {(x) - 1 }
3. 巨集定義中,最好:把每個引數都用括號括起來;整個結果表示式也應括起來。
4. 遞增(++)在巨集中的副作用
5. 巨集巢狀使用可能會產生非常長的表示式。
6. assert巨集: #define assert(e) ((void)((e)||_assert_error(__FILE__,__LINE__)))
7. 巨集與型別定義的區別
可移植性缺陷
1、應對C語言標準變更:沒看明白,估計是已經過時了,他說編譯過不了的程式在我看來是如此的正常
2、識別符號名稱的限制:這個應該也是過時
3、整數的大小
3.1、定義一個可以存放千萬數量級的數值
typedef long tenmil;
3.2、如果定義的整數跟位數有關,如需要一個無符號32位整數,則
3.2.1、這是因為int的位數與機器有關,這樣可以增強可移植性,把型別定義放在標頭檔案中
typedef uint32 int;
typedef uint32_p *uint32;
4、字元是有符號整數還是無符號整數
4.1、在字元向整數擴充套件時,如果字元的最高位為1,則有符號擴充套件,否則進行無符號擴充套件
5、位移運算子
5.1、右移時,有符號數補符號位,無符號數補0,預設是有符號數
5.2、移位計數允許的取值範圍
5.2.1 右移0~31是可以的,假設int為32位
5.3、有符號數和無符號數運算,有符號數轉換為無符號數
6、記憶體位置0:感覺不太重要,基本上應該避免讀取記憶體位置0
7、除法運算時發生截斷
7.1、這個自己在寫程式的時候也會遇到,主要是一些範圍方面的麻煩
7.2、書上沒有提出什麼有效的解決方法
8、隨機數大小
8.1、有個巨集定義了RAND_MAX,決定隨機數的大小
9、大小寫轉換
9.1、主要是說一些C語言的實現使用巨集來實現,一些使用函式來實現
10、首先釋放,然後重新分配
10.1、講的是一些挺怪異的指標使用
10.2、建議指標釋放後,就不要再嘗試對其進行解釋使用
11、可移植性問題的一個例子
11.1、例子覺得不太好,不過有個挺有意思的表示式
11.2、“0123456789”[n%10],這個表示式的含義是n的個位數代表的字元
Big Endian: 最低地址存放高位位元組,也叫高位優先。 PowerPC和SPARC處理器一般是高位優先。
Little Endian: 最低位地址存放低位位元組,也叫低位優先。 Intel x86/x86_64都採用低位優先。
附帶的作者訪談中對C++程式設計師的重要建議:
1. 避免使用指標
2. 提倡使用程式庫
3. 使用類來表示概念