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为什么这么特殊,可以直接当成空指针来用?并且它的类型就是指针,而没有经过隐式类型转换?其他数字行不行?

两个例子

img1 如图,把编译器的Wall选项(关掉了未使用变量的warning)打开,一个需要返回void *的函数,直接返回0,不会报任何warning。

img2 而如果把0改成1,就会报一个隐式类型转换的warning。所以这证明,0很特殊,可以直接当成指针类型来用,和其他整数是不一样的。

编译器实现

为什么0会被特殊对待,可以直接当成NULL来用?

一步步深究下去,我们可以去看编译器的实现。由于GCC的代码极其混乱,我们来看一下Clang的源码:

clang

代码地址:https://github.com/llvm-mirror/clang/blob/a1e00ed912a8267b04a9021df529c8f46e7db0a3/include/clang/ASTMatchers/ASTMatchers.h#L6214-L6233

注释里写得很清楚,诸如 NULL, 0, nullptr 等词,在词法分析阶段,都被直接翻译为空指针常量。因此对编译器而言他们是完全一样的。

但问题依然没有解开,为什么编译器会这么实现?我们知道,编译器肯定是按照语言的Specification来实现的,追根溯源,还是得去看C语言的Specification。

C语言Spec

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完全没有问题。

以后遇到类似的问题,应该追根溯源,这样会发现很多看起来难以理解的东西,其实真的很简单。


点击加载Disqus评论

Powered by Jekyll and Theme by solid