[心得]空指标常数(null pointer constant) -- v2

楼主: wtchen (没有存在感的人)   2016-04-28 18:52:59
根据各位板友的建议,板工完成修正版,请参考 /(_ _)\
(红色代表修正或新增的部份)
(绿色代表更新的更新)
### 在C的情况(注意:C++不适用,C++的场合请看下一节) ###
根据C11 standard 6.3.2.3 里对空指标常数 (null pointer constant)的定义:
An integer constant expression with the value 0, or such an expression cast
to type void *, is called a null pointer constant.
大意是说,空指标常数代表计算结果为零的整数常数表达式,如 0 或 0ULL,
或是前述表达式转型成 void*, 如 (void*)(0U)。
NULL是用MACRO定义,并由实作环境决定(implementation-defined)空指标常数。
对C来说,这两种写法都对:
#define NULL ((void *)0) // 一般C编译器常见的写法
#define NULL 0 // 也是符合定义的写法
((void*)0) 是空指标常数(null pointer constant),
而((int *)0) 是型别为整数指标的空指标(null pointer)**,这两者是不一样的。
比较明显的分别是,空指标常数可以被赋值给任何型别指标的左值,
而空指标只该被赋值给同样型别指标的左值(若不同型别会有warning)。
以下是一个比较清楚的例子:
int *a = 0; // 没问题,空指标常数
int *b = (int*) 0; // 没问题,右边是同型别的空指标
int *c = (void*) 0; // C没问题,右边是空指标常数,不过C++会挂
int *d = (long*) 0; // 会有warning提示 (incompatible pointer type)
int *e = (int*) a; // C不保证是空指标,C++会挂
只有常数表达式才保证会转型成空指标,
执行期间才得到的零不保证可以成功转型成空指标
这边有另一个例子:
typedef void (*func)(void); // func f = void (*f)();
func a = 0; // 没事,a是空指标常数
func b = (void *)0; // C 没问题,C++会挂
func c = (void *)(void *)0; // C 没问题,C++会挂
### 在C++的情况 ###
根据C++14 standard 4.10 里对空指标常数 (null pointer constant)的定义:
A null pointer constant is an integer literal (2.13.2) with value zero
or (以后为C++11后特有) a prvalue of type std::nullptr_t.
意思是说,它可以是0或nullptr,对C++来说,这两种写法都对:
#define NULL 0 // C++03(或之前)的作法
#define NULL nullptr // C++11(或之后)的可能作法
至于以下的情况:
int x = NULL; // 绝对不要这样写,原因请看下文
int* ptr = (void*)0; // C 没问题,C++会挂
int* ptr = 0; // C++03(或之前)与C都能用的作法
int* ptr = nullptr; // C++11以后的正统用法,
也就是上述C++14 standard里的空指标常数
为啥我们不该写出 int x = NULL 这种东西?
因为C++有C没有的函数重载(function overloading),举例来说
void DoSomething(int x);
void DoSomething(char* x);
NULL是用MACRO定义出来的,如果编译器里的NULL定义不一样,
可能会有以下两种状况:
- NULL = 0: DoSomething(NULL)会呼叫void DoSomething(int x),即
DoSomething(0)。
- NULL=nullptr: 会呼叫void DoSomething(char* x),即DoSomething(nullptr)。
C++还有一个C没有的函数样板(function template),
一样会面临到同样的问题:
void f(int* p);
template<typename T> void forward(T&& t) {
f(std::forward<T>(t));
}
int main() {
forward(NULL); // FAIL if NULL is 0
}
- NULL=0: forward函式无法分辨0是哪种型别,所以会呼叫失败
- NULL=nullptr: 可以呼叫成功,因为nullptr有他对应的型别(std::nullptr_t)。
另一个一般人比较不会留意到的分别是auto的用法:
auto p = 0; // makes auto as int
auto p = nullptr; // makes auto as decltype(nullptr)
总括来说,nullptr跟单纯的0比起来有以下的优点:
- nullptr有自己的类型(std::nullptr_t)
- 它可以转换成其他类型的指标,或是跟其他类型的指标比较
- 它不可以转换成其他类型的整数,或是跟其他类型(bool除外)的整数比较
C++的爸爸 Bjarne Stroustrup (挪抬)对于NULL是这样说的:
In C++, the definition of NULL is 0, so there is only an aesthetic difference.
I prefer to avoid macros, so I use 0. Another problem with NULL is that people
sometimes mistakenly believe that it is different from 0 and/or not an integer.
In pre-standard code, NULL was/is sometimes defined to something unsuitable
and therefore had/has to be avoided. That's less common these days.
If you have to name the null pointer, call it nullptr; that's what it's called
in C++11. Then, "nullptr" will be a keyword.
大意就是,NULL在C++03(或之前)的定义就是0,C++11开始有nullptr这个专用字眼。
由于NULL是由MACRO定义的,MACRO定义出问题可能会给程式或coder造成误解。
结论就是,C++开始还是尽量用0或nullptr*取代NULL吧。
### 感谢 ###
感谢ptt C_and_CPP板友Frozenmouse,yvb,tinlans,BlazarArc等提供修正建议。
### 参考资料 ###
- [comp.lang.c FAQ list · Question 5.2 ](http://c-faq.com/null/null2.html)
- [Is (int *)0 a null pointer?]
(http://stackoverflow.com/questions/21386995/is-int-0-a-null-pointer)
- [Why are NULL pointers defined differently in C and C++?]
(http://stackoverflow.com/questions/7016861/
why-are-null-pointers-defined-differently-in-c-and-c)
- [Should I use NULL or 0?](http://www.stroustrup.com/bs_faq2.html#null)
作者: BlazarArc (Midnight Sun)   2016-04-28 22:11:00
那个overloading的部分 我记得主要是造成template困扰
楼主: wtchen (没有存在感的人)   2016-04-28 23:43:00
正在研究跟template相关的部份...明天补充
作者: james732 (好人超)   2016-04-29 20:44:00
板主大大你自己的文章没有加分类耶XD
楼主: wtchen (没有存在感的人)   2016-04-29 20:51:00
加好了,感谢提醒,记得帮我看内文是否正确通顺喔

Links booklink

Contact Us: admin [ a t ] ucptt.com