標籤:

C/C++ 中 0 與 NULL 區別是什麼?用 delete 時,用 p=0,還是用 p=NULL 好?為什麼?

一直用null,結果老師推薦在c++中用0.

如果是C呢?


唉,終於遇到了一個有關NULL的問題,作為剛實現過編譯器的nullptr的人士,對此還是有點兒感悟的,順便吹吹牛,然後答完睡覺。

首先呢,要明白一點兒,NULL是一個無類型的東西,而且是一個宏。而宏這個東西,從C++誕生開始,就是C++之父嗤之以鼻的東西,他推崇盡量避免宏。而在他的FAQ中,也有相應的一個關於NULL與0的解釋,也談到了這一點兒。Stroustrup: C++ Style and Technique FAQ

在C++標準中,我們可以見到一個詞語叫做null pointer constant,其實在C++11標準前,是只承認0為null pointer constant的。所以,在C++中,我們也經常能聽到一個說法,就是賦予null pointer,應該是使用0,而非NULL。而nullptr pointer constant這個詞語在C++11發布後,終於再添了一個成員,就是nullptr。而與NULL本質不同的是,nullptr是有類型的(放了在stddef頭文件中),類型是 typdef decltype(nullptr) nullptr_t; 而正是因為是有類型的,這給我們編譯器實現nullptr的時候帶來了更多細節的考慮,當然也給了使用者更多的保障,所以如果你的編譯器支持nullptr,請一定使用nullptr!

而nullptr的出現背景,其實是很簡單的,C++哲學上來說就是C++之父一直對null pointer沒有一個正式的表示感到非常不滿,而更工程的來說,就是關於重載這個問題。

void f(void*)
{
}

void f(int)
{
}

int main()
{
f(0); // what function will be called?
}

而引入了nullptr,這個問題就得到了真正解決,會很順利的調到void f(void*)這個版本。

好了,真的以為nullptr就這樣了么? 我前面說過了nullptr是有類型的,叫做nullptr_t,這給我們編譯器實現帶來了諸多要考慮的東西,不幸的話讓我們來舉點兒奇葩例子吧!

union U
{
long i;
nullptr_t t;
};

int main()
{
U u;
u.i = 3;
printf("%ld
",(long)u.t); // What it is? 0 or 3?
}

那麼這是應該符合union語意還是nullptr的語意呢?這在標準中是沒有說的,我們也為此爭論了非常久。當然在我們編譯器的實現還是保持了nullptr的語意,結果是0。

而nullptr有類型後,還能做什麼呢?那當然就是可以捕獲異常了。

int main()
{
try
{
throw nullptr;
}
catch(nullptr_t)
{

}
}

你扔一個NULL試試?看他應該用什麼收,正是因為沒有類型,所以就要用它的本質類型,比如long什麼的來說。你扔一個0試試?那就也不是所謂的空指針類型了,就是要用int什麼的來收了。

所以,推崇nullptr是有道理的,我們在編譯器實現nullptr的時候考慮了非常非常多的細節,還有很多你們可能一直用不到的情況,我們都要用來測試,目的就是保障開發者的使用。再次那句話,如果你的編譯器支持nullptr,請一定使用nullptr!

最後再扯一點兒,0在C++是很神奇的東西。比如純虛函數為什麼是用=0來設置的,不知道有沒有同學去考慮過這個問題沒有。如果你深刻理解了C++哲學,這應該就是非常簡答的問題了。學語言嘛,一定要學到其哲學,你才能知道其之美,其之威力,尤其是C++。


C 語言 與 C++ 對於 NULL 的要求是不同的:

C99 標準 §6.3.2.3

3 An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.55) If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

C99 標準 §7.17

3 The macros are

NULL

which expands to an implementation-defined null pointer constant;

C++ N3797 § 4.10

1 A null pointer constant is an integer literal (2.14.2) with value zero or a prvalue of type std::nullptr_t. A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type and is distinguishable from every other value of object pointer or function pointer type. Such a conversion is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the sequence of a pointer conversion followed by a qualification conversion (4.4). A null pointer constant of integral type can be converted to a prvalue of type std::nullptr_t. [ Note: The resulting prvalue is not a null pointer value. —end note ]

C++ N3797 18.2

3 The macro NULL is an implementation-defined C++ null pointer constant in this International Standard (4.10).195

也就是說,C99 中空指針常量是一個結果是 0 的整型常量表達式 或者 這樣的表達式轉換成 (void*) 之後的東西;C++ 11 中空指針常量是一個 整型字面值常量 0 或者是 std::nullptr_t 類型的 prvalue。

而在一般標準庫的實現中,NULL 的定義大概是這樣(Visual C++ 2015 RC):

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

(如果 C++ 的 NULL 是 (void*)0,C++ 又不允許 void* 隱式轉成其它指針類型,那麼……)

那麼,為什麼我們要使用 nullptr 呢?有一個重要的原因就是模板,例如:

struct Meow
{
Meow(char*){}
};

auto kitty = std::make_shared&(NULL);//Oops!

由於 NULL 在眾多 C++ 標準庫中普遍是 0,所以 make_shared 會將參數類型推導為 int,接著 forward 給構造函數。foward 的定義大致是這樣(Visual C++ 2015 RC):

// TEMPLATE FUNCTION forward
template& inline
_Ty forward(
typename remove_reference&<_Ty&>::type _Arg) _NOEXCEPT
{ // forward an lvalue as either an lvalue or an rvalue
return (static_cast&<_Ty&>(_Arg));
}

template& inline
_Ty forward(
typename remove_reference&<_Ty&>::type _Arg) _NOEXCEPT
{ // forward an rvalue as an rvalue
static_assert(!is_lvalue_reference&<_Ty&>::value, "bad forward call");
return (static_cast&<_Ty&>(_Arg));
}

也就是說,經過它一 forward,好好的 0 變成了 (int)0,編譯器怎麼可能允許一個 int 隱式變成 char*!

這時候 nullptr 就出現啦~


NULL就是0,是個宏

#define NULL 0

C++新標準推薦使用nullptr

參考MSDN的說明:

Avoid using NULL or zero (0) as a null pointer constant; nullptr is less vulnerable to misuse and works better in most situations. For example, given func(std::pair&), then calling func(std::make_pair(NULL, 3.14)) causes a compiler error. The macro NULL expands to 0, so that the call std::make_pair(0, 3.14) returns std::pair&, which is not convertible to func()"s std::pair&parameter type. Calling func(std::make_pair(nullptr, 3.14)) successfully compiles because std::make_pair(nullptr, 3.14) returns std::pair&, which is convertible to std::pair&.

應當避免將NULL或者零(0)作為空指針常量使用。

nullptr更不容易出錯且在多數情況下工作得更好。

比如,給定一個函數func(std::pair&, double&>),然後調用func(std::make_pair(NULL, 3.14)) 會產生編譯期錯誤。

宏NULL表示0,因此調用std::make_pair(0, 3.14)的類型是std::pair&<int, double&>,並不能裝換成func()接受的std::pair&參數類型;

而調用func(std::make_pair(nullptr, 3.14))可以成功編譯,因為std::make_pair(nullptr, 3.14)返回std::pair&<std::nullptr_t, double&>,這個可以轉換為std::pair&<const char *, double&>。


NULL是個宏,跟0沒任何區別,哦,或許也可能是define NULL (void*)0 ,這樣可能更有意義.

C++推薦使用nullptr.


NULL 和 0,根據不同編譯器實現,類型是不同。NULL 可以代表當前32位(或者是 64位)的零值指針,其類型可以是int, 也可以是void*, 也可以是編譯器內建類型。

舉一個現實的例子,下面是 mingw64頭文件中的crt/stddef.h (下面是選自mingw-w64-v3.1.0)

解釋是

1)如果gcc 是c 語言模式,那麼定義NULL為 __null (編譯器內建類型)

2)如果gcc是c 語言模式而且gcc版本低於3.0,那麼定義NULL 為 void* 類型

3) 如果gcc是c++ 語言模式,而且是32位,那麼定義NULL 為無類型(?)的0

4)如果gcc是c++ 語言模式,而且是64位,那麼定義NULL 為long long 類型的0

其實這裡牽扯到了gcc 和 Windows SDK 兩個層面的東西,所以看起來會比較的有趣。其中最後一個4)是必須的,32位和64位的NULL是不同的類型,也是對於Windows 環境 令人印象深刻的一點。

PS1: 相關的一個問題,如果有興趣,也可以自己搜素下NULL, "" 和0 的區別。

PS2: 相關的另一個問題,如果有興趣,也可以自己搜索下libstdc++或者相關STL的nullptr 實現。最簡單的如libc++ 的nullptr實現 libcxx/cstddef at master · llvm-mirror/libcxx · GitHub 。另外nullptr 的類型是 std::nullptr_t,不是什麼int 或者void*。


我來總結一下:

在C++裡面,請參考 @藍色 的回答;在C裡面, @王文強的是正確答案。

展開一點的話,C++是強類型語言,又有模板、重載之類需要編譯器「依類型隨機應變」的東西;所以作為指針類型的0和整數類型的0就必須分開,不然會帶來很多麻煩。

所以,和整數大家族相關,請用0(或自己重新定義的NULL,因為某些工程/實現可能把NULL定義成nullptr或者(void*) 0);和指針大家族相關,請用nullptr;這樣才能寫出盡量不出意外的程序。

而C呢,因為少了模板、重載之類機制,就無需區分的那麼清楚了。

更進一步的話,NULL只是個宏,在前端就用0給替換掉了,不會給後端的語法分析之類邏輯看的。

所以,C裡面,NULL只是個給人看的、比較友好的選項,編譯器真正「看見」的,是0。

再細節一點的話,事實上,在某些特殊平台上,0是個合法指針。

所以照字面理解的話,if (p==0) 是不可能得到正確結果的。因為p的確有可能在運行中取得0值,而且的確指向了合法位置。

為了解決這個問題,C標準要求,在這些平台上,編譯器要把代碼中出現的、和指針的賦值/比較操作相關的0,看作一個特殊的關鍵字,在編譯時就自行替換為適合於該平台的、某個特定的用來代表非法指針的固定值。

換句話說,C裡面,NULL只是為了避免出現「魔法數字」、以方便讀程序的人而已;真正起作用的是0;並且,在某些平台上,0的地位相當於半個關鍵字。


0和nullptr是不同的的東西。C99 §6.3.2.3裡面,(int)0 或者(void*)(int)0是空指針,0L或者(float) 0不一定是(C++標準並不包含 IEEE-754)。標準只保證了編譯器接受的各種空指針值比較起來相等(compare unequal),並不要求編譯器在看到指針=0這句話的時候一定要把指針的值定義為0。C++11 N3797 §4.10裡面更進一步,只保證同類型的空指針值比較起來相等(compare unequal),也就是說,__near char* p1=nullptr;__far char* p2=nullptr, p1!=p2這樣的情況也是標準允許的。

NULL和0是不同的東西。ANSI C 4.1.5和C99 §7.17說NULL被定義為實現者決定的一個空指針。這並不意味著NULL就要被展開為(int)0 或者(void*)(int)0。展開為編譯器支持的其他nullptr值也是可以的。比如一個編譯器可以決定(__near void*) nullptr的值為0而 (__far void*) nullptr的值不是0,然後在某個僅支持__far的頭文件里把NULL定義成 (__far void*) nullptr。p=0和p=NULL之後p的值可以不同,但是因為空指針的值要比較起來相等的原因,在後續判斷是否為空指針的時候,值不同一般也不會出問題,除非像memcmp這樣繞過編譯器來比較。從代碼可讀性來說,NULL要明確一些。

編譯器支持nullptr的話就應該用nullptr而不是NULL或者0,這樣編譯器可以偷懶不去計算表達式的值或者展開宏,代碼可讀性更好,編譯速度會更快,可移植性也更好。所以條件允許的話,結果應該是兩個都不用。


應該用nullptr..0和NULL沒區別


沒有任何區別,可以使用NULL,也可以使用0,只是習慣而已,C語言本身要求在指針上下文中0會被編譯器翻譯為合適的空指針表達,使用NULL則只是一種對程序員(而不是對編譯器)的提示——這是空指針。

即使在某些空指針不等於0地址的(奇葩)機器上,你也可以給一個指針賦值為NULL或者0,編譯器會保證在指針上下文中將沒有修飾的0轉換成正確的空指針,也就是說NULL一定等於0,但是空指針不一定。

關於空指針的種種,請參考C FAQs : Null Pointers


Null 不一定是 0 ,還可能為 (void*)0,如果你用 Null

char* p = Null 等價於 char* p = (void*)0

在C++中這會帶來很嚴重的問題,因為void能隱式的轉換為其他類型,比如 (int)(*p),這樣就變得很危險。。

C++不推崇宏,加上如果你引用別人的代碼,他們已經定義了 null 的宏,但可能是 NULL 或 Null 或 null 這樣命名會變得混亂,所以推薦用 0,前提是你的編譯器還不支持 C++ 11 標準。

如果你的編譯器支持C++ 11的話,推薦使用 nullptr ,使用 0 也是很蛋疼的,比如:

void f(char*);

void f(int);

f(0); //這裡用的是哪個函數?

以上是針對 C++ 的。C 的話,由於 C 有多個標準,不同標準對於空指針的定義可能不同,使用前最好查下標準文檔。


#include &

void
func(int)
{
std::cout &<&< "int" &<&< std::endl; } void func(void *) { std::cout &<&< "void *" &<&< std::endl; } int main(int argc, char *argv[]) { func(NULL); /**&< ambiguous */ func(nullptr); /**&< right */ }


C++用0,純C語言用NULL。

C++作者在自己寫的C++教程裡面明確說過:C++定義所有類型都必須有一個共同的0值,C++標準中0值沒有類型(當然具體的編譯器是否這樣實現就看情況了)。空指針是0,假值布爾量也是0。使用0而不使用NULL是C++作者強烈推薦的寫法。

至於為什麼C++會這樣規定,可能是為了更方便的使用模板吧。因為模版被設計為能使用任何類型,此時對於未知類型需要有一個所有類型均能接受的值。

但C語言沒有定義以上特性。C語言中0值有類型。所以C語言要用NULL表示空指針。

--

至於nullptr的使用,鑒於它只支持最新語法,如果你不確定你的程序將用於什麼編譯場合,建議謹慎使用。如果目標場合確定,則可使用nullptr。不建議僅僅因為你的編譯器支持就選擇最新語法,要考慮你的目標場合。例如如果你針對嵌入式場合,那麼你的程序是有可能面對幾百種不同的 CPU 以及不同的 編譯器的。——而當我發現一份開源代碼對嵌入式友好時,通常都會認為這份代碼出自更靠譜的程序員之手。

nullptr 在功能上更優,但模型上來說 0 更優雅,因為它為所有類型提供了一個大一統的文字量,而「大一統」理論又是很多物理學家所畢生追求的,所以 0 的使用更具有美感。

用 0 或者用 nullptr 取決於你的選擇,但無論如何,C++ 使用 NULL 都是不合適的。


感覺和c++有單獨的bool類型而不是直接用char或者enum的原因一樣


在"C:Program FilesMicrosoft Visual Studio 8VCatlmfcincludeafx.h"查看這個文件可以找到NULL的定義,截圖如下:


#define NULL (void*)0


NULL是(void*)0U

也就是一個指向0地址的無類型指針。

和0的區別是它是一個指針,

而0是立即數。

在編譯的時候NULL按照指針寬度定長,

0按照一個int定長。

在邏輯中使用時並沒有什麼卵區別。就是個習慣而已,misra中規定整數型不能轉換成指針型。


首先NULL是一個宏,具體展開為什麼是由編譯器決定的(標準的說法是implementation defined)。有的定義為0, 有的C編譯器定義為(void *)0,還有定義為內部關鍵字的(GCC的__null).

另外有個噁心的事實,那就是按照C/C++標準,0能隱式轉換為任何指針類型的空指針,但是空指針的實際機器碼不一定是全0。也就是說用memset置0在某些奇葩的平台上不會講指針設為空。

最後,在C++中,最好用nullptr,如果不存在,用0比較好。因為NULL會給你錯覺,以為這是什麼特殊關鍵字,導致有的時候的重載出問題。


一直老老實實的寫NULL

雖然可以寫0.但NULL 好像更有意義些;

就好比定義一個

int a = 20;

int age = 20;

age 比 a 意思明確多了,so...拋棄0,寫NULL。


C 用 NULL,C++ 用 nullptr。

能用名字就用名字,0 也算是個 magic number。

雖然,在我寫並運行過的所有程序中,NULL 和 0 是沒有區別的。


。。。。難道不是(void *)0


推薦閱讀:

考慮R值引用,c++下多字元串連接,如何寫更高效?
C#轉C++開發,該歷經怎樣的學習路線?
如何評價Qt Lite Project?
什麼時候用異常,什麼時候用斷言?
以後想做大型遊戲(至少是端游,不是手游),不知道是不是一定需要精通C++或者熟練?

TAG:C |