C语言NULL的本质
故事的起因大致是微博上一个网友说C语言里的NULL就是0,然后一个大V挂了他,觉得他连指针和整数类型都分不清。大V评论区里的粉丝也是各种嘲讽该网友是民科,什么都不懂。
碰巧之前对C语言以及编译器有一定了解,发现很多程序员对这个看起来很基础的问题的认知都是错误的,因此写这篇文章给大家解释一下C语言里的NULL本质是个什么东西。
TL;DR 大V是错的,网友部分是对的
首先,要承认那位网友说“C语言里的NULL就是0”是有一定道理的。这里我们要纠正一个偏见,一个字面量的类型,是由编译器决定的,而不是我们在生活中觉得它是整数就一定是整数。而我们这里讨论的,也一直是“0”,而不是“整数0”。0字面量在C语言里比较特殊,并不一定代表整数0,也可以被翻译为空指针,这时候它就是指针类型。(但在Java编译器里,就没有这个特权了)
并且这不是因为C语言是弱类型语言从而进行隐式类型转换的缘故,即使底层空指针的机器码不为0也可以直接用0表示空指针。
一些C语言实现里,直接 #define NULL 0
。所以这位网友说的是有一定道理的。但又并不全对,因为还有一些实现里,#define NULL ((void *)0)
。这种情况下,说“NULL就是0”就有点问题了。
不过反过来说“0就是NULL”也是不对的,0虽然可以直接当成空指针来用,但0也可以作为整数0来使用,这时候他的类型是整数,而不是指针类型。
那么0为什么这么特殊,可以直接当成空指针来用?并且它的类型就是指针,而没有经过隐式类型转换?其他数字行不行?
两个例子
如图,把编译器的Wall选项(关掉了未使用变量的warning)打开,一个需要返回void *
的函数,直接返回0,不会报任何warning。
而如果把0改成1,就会报一个隐式类型转换的warning。所以这证明,0很特殊,可以直接当成指针类型来用,和其他整数是不一样的。
编译器实现
为什么0会被特殊对待,可以直接当成NULL来用?
一步步深究下去,我们可以去看编译器的实现。由于GCC的代码极其混乱,我们来看一下Clang的源码:
注释里写得很清楚,诸如 NULL, 0, nullptr
等词,在词法分析阶段,都被直接翻译为空指针常量。因此对编译器而言他们是完全一样的。
但问题依然没有解开,为什么编译器会这么实现?我们知道,编译器肯定是按照语言的Specification来实现的,追根溯源,还是得去看C语言的Specification。
C语言Spec
这里说得很清楚了:
“An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant”
我觉得看到这里,已经什么都不用解释了。官方定义非常清楚,它说0字面量可以当空指针来用,那就可以这么用!
后记
为什么会出现这种人为规定0可以当做空指针来用的情况呢? 这恐怕得归罪于C语言历史悠久,刚发明时,各种定义都不够规范。不像Java等强类型语言,设计时就用null关键字来表示空引用。
对C语言而言,很多东西都是后加的,恐怕0要比NULL要早出现好久。但是同时又要保证兼容性,所以出现了这个问题。 类似的例子还有不少,譬如C语言是没有原生bool类型的,用0或者1来表示true或者false完全没有问题。
以后遇到类似的问题,应该追根溯源,这样会发现很多看起来难以理解的东西,其实真的很简单。
- 上一篇 美国之行感想
- 下一篇 我与微软小冰的那些事