1. 程式人生 > >C語言中的空指標、空指標常量、NULL & 0

C語言中的空指標、空指標常量、NULL & 0

空指標:NULL還是0

看林銳博士的《高質量C/CPP程式設計》附錄的試卷,對空指標的判斷居然強制要用NULL(如 if(p==NULL) ),後來從這篇文章看到一些東西覺得有點意思。不耐煩看的人看我的歸納: 

0、0和數值“零”在指標上下文中不是一回事,0就是空指標,而不一定是“零” 
1、用0還是NULL表示空指標是風格問題,而不是對與錯的問題。 
2、空指標真的有非零的,多是罕見機器。但此時 0 在指標上下文中會自動轉為合適的空指標。 
3、用 if(p), if(!p) 還是 if(p!=NULL), if(p==NULL) 都完全合法。 
4、NULL一般被定義為0或(void*)0 
5、0作為函式實參時,為了表示它是空指標,最好把它至於指標上下文中,即加上(char*)或(void*)修飾。(這要看編譯器了,我在gcc4.1下就不需要修飾)。 

================================================= 
原文出處:

http://c-faq-chn.sourceforge.net/ccfaq/ 
部分文摘 
================================================= 
6.2 怎樣在程式裡獲得一個空指標? 
根據語言定義, 在指標上下文中的常數 0 會在編譯時轉換為空指標。也就是說, 在初始化、賦值或比較的時候, 如果一邊是指標型別的值或表示式, 編譯器可以確定另一邊的常數 0 為空指標並生成正確的空指標值。因此下邊的程式碼段完全合法: 

char *p = 0; 
if(p != 0) 

參見問題 5.3。 

然而, 傳入函式的引數不一定被當作指標環境, 因而編譯器可能不能識別未加修飾的 0 ``表示" 指標。在函式呼叫的上下文中生成空指標需要明確的型別轉換, 強制把 0 看作指標。例如, Unix 系統呼叫 execl 接受變長的以空指標結束的字元指標引數。它應該如下正確呼叫: 

execl("/bin/sh", "sh", "-c", "date", (char *)0); 

如果省略最後一個引數的 (char *) 轉換, 則編譯器無從知道這是一個空指標, 從而當作一個 0 傳入。(注意很多 Unix 手冊在這個例子上都弄錯了。) 

如果範圍內有函式原型, 則引數傳遞變為 “賦值上下文", 從而可以安全省略多數型別轉換, 因為原型告知編譯器需要指標, 使之把未加修飾的 0 正確轉換為適當的指標。函式原型不能為變長引數列表中的可變引數提供型別。 (參見問題 15.3) 在函式呼叫時對所有的空指標進行型別轉換可能是預防可變引數和無原型函數出問題的最安全的辦法。 


============================ 
6.3 用縮寫的指標比較 "if(p)" 檢查空指標是否可靠?如果空指標的內部表達不是 0 會怎麼樣? 
當 C 在表示式中要求布林值時, 如果表示式等於 0 則認為該值為假, 否則為真。換言之, 只要寫出 

if(expr) 

無論"expr" 是任何表示式, 編譯器本質上都會把它當 

if((expr) != 0) 

處理。 

如果用指標 p 代替 "expr" 則 

if(p) 等價於 if(p != 0)。 

而這是一個比較上下文, 因此編譯器可以看出 0 實際上是一個空指標常數, 並使用正確的空指標值。這裡沒有任何欺騙; 編譯器就是這樣工作的, 併為、二者生成完全一樣的程式碼。空指標的內部表達無關緊要。 

布林否操作符 ! 可如下描述: 
!expr 本質上等價於 (expr)?0:1 
或等價於 ((expr) == 0) 
從而得出結論 
if(!p) 等價於 if(p == 0) 
類似 if(p) 這樣的 "縮寫", 儘管完全合法, 但被一些人認為是不好的風格 (另外一些人認為恰恰是好的風格; 參見問題 17.8)。 

參見問題 9.2。 

============================ 
6.4 NULL 是什麼, 它是怎麼定義的? 
作為一種風格, 很多人不願意在程式中到處出現未加修飾的 0。因此定義了預處理巨集 NULL (在 <stdio.h> 和其它幾個標頭檔案中) 為空指標常數, 通常是 0 或者 ((void *)0) (參見問題 5.6)。希望區別整數 0 和空指標 0 的人可以在需要空指標的地方使用 NULL。 

使用 NULL 只是一種風格習慣; 前處理器把所有的 NULL 都還原回 0, 而編譯還是依照 上文的描述處理指標上下文的 0。特別是, 在函式呼叫的引數裡, NULL 之前 (正如在 0 之前) 的型別轉換還是需要。問題 5.2 下的表格對 0 和 NULL 都有效 (帶修飾的 NULL 和帶修飾的 0 完全等價)。 

NULL 只能用作指標常數; 參見問題 5.7。 

============================ 
6.14 說真的, 真有機器用非零空指標嗎, 或者不同型別用不同的表達? 
至少 PL/I, Prime 50 系列用段 07777, 偏移 0 作為空指標。後來的型號使用段 0, 偏移 0 作為 C 的空指標, 迫使類似 TCNP (測試 C 空指標) 的指令明顯地成了現成的作出錯誤猜想的蹩腳 C 程式碼。舊些的按字定址的 Prime 機器同樣因為要求位元組指標 (char *) 比字指標 (int *) 長而臭名昭著。 

Data General 的 Eclipse MV 系列支援三種結構的指標格式 (字、位元組和位元指標), C 編譯器使用了其中之二:char * 和 void * 使用位元組指標, 而其它的使用字指標。 

某些 Honeywell-Bull 大型機使用位元模式 06000 作為 (內部的) 空指標。 

CDC Cyber 180 系列使用包含環 (ring), 段和位移的 48 位指標。多數使用者 (在環 11 上) 使用的空指標為 0xB00000000000。 在舊的 1 次補碼的 CDC 機器上用全 1 表示各種資料, 包括非法指標, 是十分常見的事情。 

舊的 HP 3000 系列對位元組地址和字地址使用不同的定址模式; 正如上面的機器一樣, 它因此也使用不同的形式表達 char * 和 void * 型指標及其它指標。 

Symbolics Lisp 機器是一種標籤結構, 它甚至沒有傳統的數字指標; 它使用 <NIL, 0> 對 (通常是不存在的 <物件, 偏移> 控制代碼) 作為 C 空指標。 

根據使用的 ``記憶體模式", 8086 系列處理器 (PC 相容機) 可能使用 16 位的資料指標和 32 位的函式指標, 或者相反。 

一些 64 位的 Cray 機器在一個字的低 48 位表示 int *; char * 使用高 16 位的某些位表示一個位元組在一個字中的偏移。 


============================ 
6.7 如果 NULL 和 0 作為空指標常數是等價的, 那我到底該用哪一個呢? 
許多程式設計師認為在所有的指標上下文中都應該使用 NULL, 以表明該值應該被看作指標。另一些人則認為用一個巨集來定義 0, 只不過把事情搞得更復雜, 反而令人困惑。因而傾向於使用未加修飾的 0。沒有正確的答案。 (參見問題 9.2 和 17.8) C 程式設計師應該明白, 在指標上下文中 NULL 和 0 是完全等價的, 而未加修飾的 0 也完全可以接受。任何使用 NULL (跟 0 相對) 的地方都應該看作一種溫和的提示, 是在使用指標; 程式設計師 (和編譯器都) 不能依靠它來區別指標 0 和整數 0。 

在需要其它型別的 0 的時候, 即便它可能工作也不能使用 NULL, 因為這樣做發出了錯誤的格式資訊。(而且, ANSI 允許把 NULL 定義為 ((void *)0), 這在非指標的上下文中完全無效。特別是, 不能在需要 ASCII 空字元 (NUL) 的地方用 NULL。如果有必要, 提供你自己的定義 

#define NUL '\0'