当空指针不全为零时如何正确编写C / C ++代码


69

正如comp.lang.c常见问题解答所说,在某些体系结构中,空指针并非全为零。所以问题是什么实际上检查以下构造:

void* p = get_some_pointer();
if (!p)
    return;

我是在p与机器相关的空指针进行比较p,还是在与算术零进行比较?

我应该写

void* p = get_some_pointer();
if (NULL == p)
    return;

而是为这种架构做好准备,还是只是我的偏执?


33
这是一个C还是C++问题吗?在C++你应该总是使用nullptr
BeyelerStudios

4
空指针的实际位表示形式是实现细节,只要您编写标准c就不会引起您的兴趣。由于定义了if(!p),因此可以回答您的问题。对于其他任何怪异的实现也是如此-遵循标准,这是编译器的问题,需要弄清楚如何使计算机达到您的期望。
Voo 2015年

5
顺便说一句,NULL算术零,至少在C ++中(我也认为在C中以强制转换为模)。这是真实的,即使底层的地址不是物理内存地址为零。也就是说,在C ++中,你可以把NULL0相同的,他们实际上没有什么区别。
康拉德·鲁道夫

4
其结果之一是,memset为零可能会导致非NULL指针。顺便说一句,可能遇到此问题的奇异硬件的类型可能会破坏关于现代体系结构的许多其他常见假设。更重要的是,尽管我很欣赏在实践中编写可移植代码的愿望,但除非实际已经在深奥的硬件上进行了测试,否则非平凡的C / C ++代码永远无法在这些类型的遥不可及的平台上运行,至少这是我的经验。
doynax

2
如果需要使用两种不同语言的答案,则应该有两个单独的问题,正确的执行方式在C和C ++中不一定相同(尤其是在C ++中为nullptr)
价值

Answers:


101

根据C规范:

值为0的整数常量表达式,或强制转换为void *类型的表达式,称为空指针常量。55)如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不相等的指针与指向任何对象或函数的指针进行比较。

所以0是一个空指针常数。如果将其转换为指针类型,则将获得一个空指针,对于某些体系结构,该指针可能不是全零位。接下来,让我们看看规范关于比较指针和空指针常量的内容:

如果一个操作数是一个指针,另一个是空指针常量,则将空指针常量转换为指针的类型。

让我们考虑一下(p == 0):首先0将其转换为空指针,然后p将其与一个空指针常量进行比较,该常量的实际位值取决于体系结构。

接下来,查看规范关于否定运算符的内容:

逻辑否定运算符的结果!如果其操作数的值比较不等于0,则为0;如果其操作数的值比较等于0,则为1。结果的类型为int。表达式!E等效于(0 == E)。

这意味着,按照规范,这(!p)等效于针对机器定义的空指针常量进行测试。(p == 0)p

因此,if (!p)即使在空指针常量并非全零位的体系结构上,您也可以安全地编写。

对于C ++,空指针常量定义为:

空指针常量是整数类型的整数常量表达式(5.19)prvalue,其值为零,或类型为std :: nullptr_t的prvalue。空指针常量可以转换为指针类型。结果是该类型的空指针值,并且可以与对象指针或函数指针类型的所有其他值区分开。

这与C语言的nullptr语法和语法糖差不多。操作员的行为==定义为:

另外,可以比较指向成员的指针,或者指向成员的指针和空指针常量。执行指针到成员转换(4.11)和资格转换(4.4)的操作,以将它们转换为通用类型。如果一个操作数是空指针常量,则公共类型是另一操作数的类型。否则,通用类型是指向类似于操作数之一的类型的成员类型(4.4)的指针,并且具有cv限定签名(4.4),该签名是操作数类型的cv限定签名的并集。[注意:这意味着可以将指向成员的任何指针与一个空指针常量进行比较。—尾注]

这导致转换为0指针类型(与C一样)。对于求反运算符:

逻辑否定运算符的操作数!在上下文中转换为bool(第4条);如果转换后的操作数为true,则其值为true;否则为false。结果的类型是布尔。

这意味着的结果!p取决于如何bool执行从指针到的转换。该标准说:

零值,空指针值或空成员指针值将转换为false;

因此if (p==NULL)if (!p)在C ++中也做同样的事情。


8
恕我直言,这是唯一完整的答案,就好像指出所定义的标准!EE == 0 (C11草案6.5.3.3/7)一样。–
alk

1
您是否对引用运算符的C ++标准作了错误引用?它不应具有type的结果int。根据C ++ 11 5.3.1 / 9,“结果的类型为bool”。
Ruslan

非常详细的答案,太好了
user1 2015年

这个答案使我确信这if (! p)是明确的。但是呢if (p)?这看似荒谬,但此答案中的引号实际上并不能保证其语义。我相信§6.8.4.1p2可以。
康拉德·鲁道夫

32

在实际机器中,空指针是否为全零都没关系。假设p是一个指针:

if (!p) 

始终是测试是否p为空指针的合法方法,并且始终等效于:

if (p == NULL)

您可能对另一篇C-FAQ文章感兴趣:这很奇怪。NULL是否保证为0,但null指针不是?


以上对于C和C ++均适用。请注意,在C ++(11)中,首选将其nullptr用于空指针文字。


3
为什么链接文章中的陈述不完整?它们都以“ ...”结尾,为什么呢?
Marson Mao

2
@MarsonMao因为每个人都以下一条语句的引言结尾。
Ixrec 2015年

12

此答案适用于C。

不要NULL与空指针混淆。NULL只是一个保证为空指针常量的宏。空指针常量保证为0(void*)0

从C11 6.3.2.3:

值为0的整数常量表达式,或强制转换为void *类型的表达式,称为空指针常量66)。如果将空指针常量转换为指针类型,则保证生成的指针(称为空指针)将不相等的值与指向任何对象或函数的指针进行比较。

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

7.19:

宏是

空值

扩展为实现定义的空指针常量;

在的情况下定义的实现NULL0(void*)0NULL不能是别的。

但是,将空指针常量分配给指针时,您会得到一个空指针,即使它等于一个空指针常量,它也可能不具有零值。该代码if (!p)NULL宏无关,您将空指针与算术值零进行比较。

因此,从理论上讲,类似这样的代码int* p = NULL可能会导致一个空指针p,该指针不同于零。


相关问题得到了一些不错的答案。
伦丁2015年

2
空指针常量可以是一个ICE,其计算结果为0,例如,NULL可以定义为(1 - 1)
MM

“实现定义在空的情况下,为0或无效*” -想必你的意思是(void *)0
MM

1
“空指针,不一定等于零。” -空指针保证等于0
MM

1
一元!运算符被定义为使得!x相同x == 0,由于x == 0具有相对于预期的效果NULL指针,!x不检查用于NULL一平台,其中上NULL指针不数值等于0
FUZ

7

过去,STRATUS计算机在所有语言中的空指针均为1。

这给C带来了问题,因此他们的C编译器将允许指针比较0和1返回true。

这将允许:

void * ptr=some_func();
if (!ptr)
{
    return;
}

return在一个空PTR,即使你可以看到,ptr在调试器中有值1

if ((void *)0 == (void *)1)
{
    printf("Welcome to STRATUS\n");
}

实际上会打印“ Welcome to STRATUS”


1
空指针常量可以具有任何表示形式不是历史的,也不是单体系结构的扩展。这是两种语言的核心事实。毕竟,抽象是这些语言的全部目的。
Lightness Races in Orbit

@LightnessRacesinOrbit我不相信这一点。C标准实际上被迫支持带有设置位的空指针,因为那里已经有实现带有空指针的实现。如果所有实现都将全零位设置为空指针,那么我非常相信该标准会要求这样做。后来对于整数类型是必需的,因为无论如何它都是所有实现的工作方式,并且它对于程序(calloc如这样的程序)很有用。那个完全相同的参数如何不适用于指针类型?

@hvd:我不相信。强制执行该标准是什么业务?标准作者不仅(如您所说)知道过去是这种情况,而且更重要的是,将来任何时候都可能成为这种情况,并且没有理由将其限制在语言抽象中。
Lightness Races in Orbit

@LightnessRacesinOrbit那么,您认为整数类型和指针类型之间的区别是什么?是什么使得可以要求所有零代表零,但不能要求所有零代表空指针呢?我能想到的唯一重要区别是,过去有实际的实际实现,其中所有位为零都不表示空指针。对于整数类型,也有合理的理由使所有位为零表示除零以外的其他值,那么为什么您的参数在那里不适用?

1
出于好奇,我找到了描述Stratus计算机的空指针的文档:ftp.stratus.com/vos/doc/reference/page_0_control.txt
Tor

1

如果您的编译器不错,则有两件事(只有两件事)需要注意。

1:静态默认初始化(即未分配)的指针中将没有NULL。

2:结构或数组上的memset()或扩展名calloc()不会将指针设置为NULL。


2
您对(2)正确,但对(1)错误。C ++ 03第3.6.2节说“静态存储持续时间的对象应进行零初始化(8.5)”,第8.5(5)节将标量类型T的“零初始化”定义为将常量0转换为类型T。因此,即使NULL并非全零,静态持续时间指针也被初始化为NULL。(C规范包含类似的措词。)
Nemo

C ++标准规定,没有初始化程序的静态存储持续时间指针必须初始化为nullptr。如果您的编译器不执行此操作,那就不好了……
MM

1
@MattMcNabb:我认为他的意思是“如果您的编译器有错误,则可能还有其他情况”。
哈里·约翰斯顿

1
@Joshua:正如Matt所说,每个C和C ++标准的每个版本都保证静态持续时间指针正确地初始化为NULL,就像您编写了一样... = 0;。对于以前的每个符合标准的编译器,您都(1)错了。
Nemo

1
我可以发誓我的书说它是二进制0,这是我曾经在一个没有NULL =二进制零的平台上使用过的唯一一本书。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.