【C++】特殊字符“\0”,以及NULL相關


我們都知道,’\0’是字符串的結束標記。因此,執行這段代碼:

#include<bits/stdc++.h>
using namespace std;
int main(){
    cout<<"ab\0cd";
}

 輸出結果:ab

這是因為,cout默認判斷字符串到結束符號\0,認為字符串結束了,因此就停止。

事實上,\0是一個非打印字符,也就是不能被打印出來的字符。如果直接嘗試使用cout或者putchar輸出\0,什么也不會發生。ascii碼為0-31之間的字符都是非打印字符。

 

下面內容引用自《征服C指針》,是NULL相關內容。注意0對應的字符不是NULL,NULL表示空指針,不是字符。

(NULL是來自stdio的一個宏定義,一般是這樣:#define NULL ((void*)0) )

 

補充NULL、0 和'\0'

經常有一種錯誤的程序寫法:使用NULL來結束字符串。

/*通常,C 的字符串使用''結尾,可是因為strncpy()函數在 src 的長度大於len的情況下沒有使用'\0'來結束,所以一板一眼地寫了一個整理成C 的字符串形式的函數(企圖)/

void my_strncpy(char dest, char src, int len) { 

strncpy(dest, src, len); 

dest[len] = NULL; ←使用NULL 來結束字符串!!

}

上面的代碼,盡管在某些運行環境下能跑起來,但無論怎樣它就是錯誤的。因為字符串是使用“空字符”來結束的,而不是用空指針來結束。

在 C 語言標准中,空字符的定義為“所有的位為 0 的字節稱為空字符(nullcharacter)”(5.2.1)。也就是說,空字符是值為 0 的字符。空字符在表現上通常使用'\0'。因為'\0'是常量,所以實際上它等同於 0。也許有些嚇到你了,'\0'呀'a'呀什么的,它們的數據類型其實並不是char,而是int*。

* 如果是C++,就不是這個結論了。

另外,在我的環境中,NULL在 stdio.h 里的定義如下:

#define NULL 0看到這個,你可能會說:“說來說去,那還不都是 0 嘛。”確實在大部分的情況下是這樣的,但背后的事情卻異常復雜。正如前面說的那樣,寫成'\0'和寫成常量的0其實是一樣的。使用'\0'只不過是習慣使然。如果想讓代碼容易讀,遵從習慣是非常重要的。

將0當作空指針來使用,除了極其例外的情況,通常是不會發生錯誤的。但是,如果在字符串的最后使用NULL,就必然會發生錯誤。標准允許將NULL定義成(void*)0,所以在NULL被定義成(void*)的時候,如果使用NULL來結束字符串,編譯器必然會提示警告。

看到剛才的關於NULL的定義,可能有人會產生下面的推測:啥呀?所謂空指針,不就是為 0 的地址嘛。在 C 中,為 0 的地址上應該是不能保存有效數據的吧?放什么都起不到任何作用,這沒什么大不了的。這種推測好像頗有道理,但也是有問題的。確實在大多數的環境中,空指針就是為 0 的地址。但是,由於硬件狀況等原因,世上也存在值不為 0 的空指針。偶爾會有人在獲得一個結構體之后,先使用memset()將它的內存區域清零然后再使用。

此外,雖然 C 語言提供了動態內存分配函數malloc()和calloc(),但是抱着“清零后比較好”的觀點,偏愛 calloc()的人倒有很多。這樣也許可以避免一些難以再現的bug。使用memset()和calloc()將內存區域清零,其實就是單純地使用 0 來填充位。通過這種處理,當結構體的成員中包含指針的時候,這個指針能不能作為空指針來使用,最終是由運行環境來決定的。順便說一下,對於浮點數,即使它的位模式為 0,值也不一定為 0*。

* 整數類型還好,但是我還是感覺依賴環境編出來的代碼是不干凈的。

說到這里,哦,原來這樣啊,所以要使用宏定義的NULL呢。對於空指針的值不為 0 的運行環境,NULL的值應該被#define成別的值吧。可能會有人產生以上的想法。實際上,這種想法也是有偏差的,這涉及問題的內部根源。

比如,嘗試編譯下面的代碼:int *p = 3;在我的環境里,會出現以下警告:warning: initialization makes pointer from integer without a cast因為 3 無論怎么說都是int型,指針和int型是不一樣的,所以編譯器會提示警告。盡管在我的環境里指針和int的長度都是 4 個字節,但還是出現了警告。如今的編譯器,幾乎都是這樣的。繼續,讓我們嘗試編譯下面的代碼:int *p = 0;這一次沒有警告。

如果說將int型的值賦予指針就會得到一個警告,那么為什么值為 3 的時候出現警告,值為 0 的時候卻沒有警告呢?簡直匪夷所思!這是因為在 C 語言中,“當常量 0 處於應該作為指針使用的上下文中時,它就作為空指針使用”。上面的例子中,因為接受賦值的對象為指針,編譯器根據上下文判斷出“0應該作為指針使用”,所以將常數 0 作為空指針來讀取。無論如何,編譯器都會針對性地對待“需要將 0 作為指針進行處理的上下文”,所以即便是空指針的值不為 0 的情況下,使用常量 0 來代替空指針也是合法的。

此外,如上所述,有的環境中像下面這樣定義NULL:#define NULL ((void*)0)

ANSI C 中,根據“應該將 0 作為指針進行處理的上下文”的原則,將常量 0 作為指針來處理。因此,顯式將 0 強制轉型成void*是沒有意義的。但是在某些情況下,編譯器也可能會理解不了“應該將 0 作為指針進行處理的上下文”。這些情況是:

沒有原型聲明的函數的參數

可變長參數函數中的可變部分的參數

ANSI C 中,因為引入了原型聲明,只有在你確實做了原型聲明的情況下,編譯器才能知道你“想要傳遞指針”。可是,對於以printf()為代表的可變長參數函數,其可變部分的參數的類型編譯器是不能理解的。

另外糟糕的是,在可變長參數的函數中,還經常使用常量NULL來表示參數的結束(比如 UNIX 的系統調用execl()函數)。以上情況下,簡單地傳遞常量 0,會降低程序的可移植性。因此,通過使用宏定義NULL來將 0 強制轉型成void*,可以顯式地告之編譯器當前的0 為指針*。

* 關於這個話題,在 C 語言 FAQ(http://www.catnet.ne.jp/kouno/c_faq/c_faq.htm)中,也花費了一章的筆墨進行了討論。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM