在C中,NULL和0是否完全等效?

时间:2015-02-25 11:24:14

标签: c null null-pointer

在C89 / C99 +中使用NULL优先于0是否有“硬”原因,并且在不考虑与标准合规性有关的深层关注的情况下进行交换,即使在使用C的非常模糊的方面的代码中也是如此。

我关注标准合规性,可移植性,未定义的行为等“硬”事物,意外地与语言的意外角落进行不同的交互,是否(假设的)Milliard Gargantubrain分段记忆超级计算机可能释放其魔法烟雾等你用一个代替另一个。

在这个网站上已经存在类似的关于C ++的问题,但我不关心C ++。我相信这是C ++行为可能与C不同的领域之一。

风格和意图的问题虽然重要,但并不认为在Q& A论坛中讨论它们是主题性的或有用的。

我和其他人发现的答案主要涉及测试和分配(我们通常会看到,并且可以互换使用它们是安全的)。我问是否有任何想法需要采取你已经找到的任意奇怪但符合标准的代码,并在语法上将NULL替换为0(或者当0是指针时可能反之亦然),无论是否是做风格的明智之举。很难给出意想不到的互动的例外,但是,例如,功能指针经常会把我们赶出去,也许是尺寸,......

我已经阅读了描述0和NULL如何表现的相关标准部分,有人可以帮助我在不明显的情况下对指针常量0和NULL的交换产生影响吗?或者向我保证没有。

3 个答案:

答案 0 :(得分:5)

明确您的代码意图。

NULL指针。

00u0l等等。

双倍的

0.00..0

浮点数为

0.0f0.f.0f

'\0'为无字符。

在Objective-C中,nil表示nil对象。

答案 1 :(得分:2)

在使用 NULL 的任何地方使用常量 0(或任何计算为 0 的常量表达式)都是安全的,因为 0 是一个空指针常量

这在 C99(和 C11)标准状态的第 6.3.2.3p3 节中定义:

<块引用>

值为 0 的整数常量表达式,或这样的 转换为 void * 类型的表达式,称为 空指针 常量.55) 如果将空指针常量转换为指针类型, 结果指针,称为空指针,保证 比较不等于指向任何对象或函数的指针。

因此,0 在转换为指针类型时,无论特定系统如何实现空指针,都保证会转换为空指针。

来自上述引用的脚注 55(C11 中的 66)指出:

<块引用>

NULL<stddef.h>(和其他头文件)中定义为空指针常量。

以我的 CentOS7 系统为例,它定义 NULL 为:

#define NULL ((void *)0)

虽然使用 0 代替 NULL 是安全的,但它并不完全等效,例如,如果在上下文中使用它并不一定需要指针。例如,sizeof(0) 将评估为 int 的大小,而 sizeof(NULL) 将(至少在我的系统上)评估为 void * 的大小。

相反,使用 NULL 代替 0,则不一样。如果你有这个:

int x[5] = { 3, 4, 5, 6, 7 };
int *p = x + 0;

并将其替换为:

int x[5] = { 3, 4, 5, 6, 7 };
int *p = x + NULL;

代码将无法编译,因为两个指针不能作为 + 运算符的操作数。

另一个例子:

int x[5] = { 3, 4, 5, 6, 7 };
void *p = (void *)x - 0;
printf("*p = %d\n", (int *)p);

虽然不严格符合标准,但某些实现(如 gcc)允许对 void * 类型进行指针运算,并会导致打印 3。如果您为 NULL 更改了 0:

int x[5] = { 3, 4, 5, 6, 7 };
void *p = (void *)x - NULL;
printf("*p = %d\n", (int *)p);

如果您在 1) 允许 void * 算术和 2) 使用非零地址作为空指针的系统上运行此代码,您将创建和取消引用指向数组开始之前的指针并且可能没有正确对齐。

答案 2 :(得分:0)

C 标准特别允许实现做

#define NULL 0

(http://port70.net/~nsz/c/c11/n1570.html#7.19p3 : "宏是 NULL,它扩展为一个实现定义的空指针常量";0 是一个空指针常量。)

这在 language-lawyer-ese 中的意思是,将标识符 NULL 的每个实例更改为整数常量 0 的搜索和替换不能改变任何严格遵守程序的含义。 >

然而,很难确保程序严格符合要求。其他答案给出了用 0 替换 NULL 实际上可以改变程序含义的例子。我想指出相反的情况:如果程序由于 0 被解释为整数常量而不是空指针常量而出现错误,0 替换 NULL不修复错误

一个简单的例子是 execlp 的任何使用:

execlp("ls", "ls", "-l", 0);  // UB: last argument is passed as (int)0
                              // rather than (char *)0 as expected by callee

execlp("ls", "ls", "-l", NULL);         // still buggy, NULL could expand to 0

execlp("ls", "ls", "-l", (char *)0);    // correct
execlp("ls", "ls", "-l", (char *)NULL); // correct but asking for someone to
                                        // "simplify" the code by removing the cast

当您处理并非每个函数都使用原型声明的旧代码时,这种类型的问题会变得更加常见。