C++的零指標(NULL,0,nullptr)
零指標的定義
C語言:NULL
C++03前:0
C++11:nullptr
進化之路
最開始,C語言中的NULL通常定義成
#define NULL ((void *)0)
// C語言有隱式指標轉換,可以寫如下程式碼
int *i = NULL;
time_t* t = NULL;
然而C++語言要求更為嚴格,不允許進行這樣的隱式轉換,而是直接使用字面值0作為零指標,為了相容之前的習慣,NULL會進行這樣的定義
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
不過當時C++的書籍都推薦用0而不是NULL,這是為什麼呢?我所參考的原文描述的原因是這樣的:
因為C++有函式過載,考慮如下程式碼
void foo( int a, int* b );
// 其它檔案中呼叫如下程式碼
foo( 1, NULL );
foo( 1, 0 );
呼叫正常,如果我們添加了一個過載函式
void foo( int a, int* p );
void foo( int a, int b );
// 其它檔案中呼叫如下程式碼
foo( 1, NULL );
foo( 1, 0 );
對於NULL版本,我們可能無法意識到錯誤,因為我們一直會認為NULL就應該呼叫指標版本,而對於0來說,我們很快能夠發現呼叫了整數版本,從而調整我們的呼叫方式:
foo( 1, static_cast<int*>(0 ) );
可是,我自己的實驗結果是這樣的
void f( int a, int* p );
foo( 1, NULL ); // ok
foo( 1, 0 ); // ok
void f( int a, int* p );
void f( int a, int b );
foo( 1, NULL ); // compile error, ambiguous
foo( 1, 0 ); // ok, but call f(int, int)
也就是在這種情況下,NULL和0並不等價,NULL版本直接給了編譯錯誤,而0版本直接偷偷遷移到新的函式,我們可以得知,NULL和0並不是完全等價,目前來看,NULL比0似乎有了一點優勢。
讓我們看看NULL到底是怎麼定義的,一般NULL定義在stddef.h裡,我找到了gcc 4.9.1中是這麼定義的:
/* A null pointer constant. */
#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL /* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0)
#else /* C++ */
#define NULL 0
#endif /* C++ */
#endif /* G++ */
#endif /* NULL not defined and <stddef.h> or need NULL. */
#undef __need_NULL
可以看到除了一個__null,其餘部分和上面介紹的是一致的,所以g++編譯器一定是用了__null,其實,這個__null是在gcc編譯器原始碼中定義的,具體查詢過程請見參考條目[1],我本想將其原始碼也貼過來的,後來覺得也沒有必要,因為看了原始碼也不能立刻明白是怎麼回事。相關原始碼檔案是:
- gcc/c-family/c-common.h
- gcc/c-family/c-common.c
- gcc/ginclude/stddef.h
- gcc/cp/parser.c
也就是說,聰明的編譯器會在遇到__null的時候,做特殊語法分析,從而讓NULL表現的和整數0能夠相容,又不完全一樣。在NULL當成0使用的時候,儘可能的給出警告,例如
void foo( int a, int b ); // 只有這個定義
foo( 1, NULL ); // g++會給出警告
編譯器會盡可能提醒使用者NULL應該作為指標,類似的,clang編譯器也有一個__null的定義。
不管怎麼樣,以上的方案都是讓人迷茫的,也都是不徹底的,所以C++11推出了nullptr關鍵字來表示零指標,它是一個C++語言的關鍵字,所以不要問是在哪裡定義的,它的身份已經和NULL不同了。另外它的這個型別是std::nullptr_t,可以在stddef.h中發現這樣的程式碼
namespace std { typedef decltype(nullptr) nullptr_t; }
我們應該在未來的程式碼中儘量多的使用nullptr,目前,C++程式碼庫中的程式碼大部分還是使用的NULL。