1. 程式人生 > >C++的零指標(NULL,0,nullptr)

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。