空指针和指针算术运算

13

考虑以下代码,对 nullptr 进行指针算术运算是否安全?

我认为在一个 nullptr 上加任何偏移量都会得到另一个 nullptr,目前 MSVC 生成了我预期的结果,但我对像这样使用 nullptr 是否安全有点不确定。

float * x = nullptr;

float * y = x + 31; // I assume y is a nullptr after this assigment

if (y != nullptr)
{
  /* do something */
}

这在clang和gcc中不会发生,float* a = nullptr; assert(a==nullptr); a++; assert(a!=nullptr); 。在这些编译器中,将某些内容添加到nullptr并不会导致其变成nullptr。我不知道这是否属于未定义行为。 - alfC
一个简短的注释,因为没有答案包含这个:在实践中,指针是内存地址。如果你天真地给空指针加上偏移量31,你会得到一个指向内存地址“31”的指针。那不是空指针。编译器允许这样做。如果将空指针+偏移定义为空指针,编译器就必须在那里添加一些条件跳转。由于委员会关心效率,标准没有规定当你给空指针加上非零偏移量时会发生什么。 - Handy999
5个回答

12

你没有定义"safe"对你来说意味着什么,但无论如何,你提出的代码具有未定义的行为。指针算术运算只允许在指向数组对象的指针值上进行,或者可能是指向数组结尾后面的位置。(对于这个规则,非数组对象被视为一个元素的数组。)

由于空指针从不是对象的地址或对象之后的地址,因此你的代码永远不会具有良好定义的行为。


1
即使我不解引用指针,int* a; a++;也是未定义行为吗?你能给出一个参考吗? - 463035818_is_not_a_number
5
因为标准不必明确指定未定义行为,所以它没有相应的参考文献。[expr.add]/5 仅针对指向数组(或充当长度为1的数组的对象的指针)的指针定义了指针+整数的行为。如果指针不指向数组,则根据定义,行为是未定义的(除非在添加0的情况下,[expr.add]/8 为所有指针定义了行为)。 - Nicol Bolas
3
@tobi303,"UB"并不是指“崩溃”,而是指“我们没有定义这个该怎么做”。事实上,编译器可以积极地优化和检测UB,并导致您的程序表现出极其意外的行为。其中一种优化技术是“由于在格式良好的程序中不可能发生UB,因此任何导致UB的逻辑链都可以被安全地排除为可能性,包括跳过代码中明确的if检查”。简而言之,UB可能会导致时间旅行漏洞,其中远离UB的代码的行为与您编写的代码不匹配。 - Yakk - Adam Nevraumont
@NicolBolas 谢谢。我在询问参考时,想要的是类似[expr.add]/5这样的内容。 - 463035818_is_not_a_number
1
@Yakk 我(或多或少)知道 UB 的意思,只是一个小时前我才意识到 int* a;a++ 是 UB。 - 463035818_is_not_a_number

10
在nullptr上进行指针算术运算是否安全?C++定义了两种对nullptr的操作:
float *x = nullptr;
float *y = nullptr;
  1. x +/- 0 = x

  2. x - y = 0 (xy 具有相同的类型)

你不能对未定义的内容进行假设,因此不应该这样做。


3
实际上,还定义了第三个操作符:x==y。看起来微不足道,但它实际上意味着只有一个空指针值。 - MSalters
1
一个有趣的帖子:http://www.drdobbs.com/cpp/why-does-c-allow-arithmetic-on-null-poin/240001022 - Zark Bardoo

4

0添加到nullptr具有明确定义的行为,并返回nullptr

将任何其他内容添加到nullptr具有未定义的行为。

[expr.add]/4

当将具有整数类型的表达式J添加到或从指针类型的表达式P中减去时,结果具有P的类型。

  • 如果P评估为null指针值且J评估为0,则结果是null指针值。
  • 否则,如果P指向具有n个元素([dcl.array])的数组对象x的数组元素i,则表达式P + J和J + P(其中J具有值j)指向x的(可能是假想的)数组元素i+j,如果0≤i+j≤n,则表达式P - J指向x的(可能是假想的)数组元素i−j,如果0≤i−j≤n。
  • 否则,行为未定义。
第一个要点涵盖了添加零,第二个要点涉及到数组,最后一个要点涵盖了其他所有内容。
由于我们从未有过数组,我们可以忽略第二个要点,只需处理第一个和第三个。这给我们带来的结果是,添加零是定义行为,添加任何其他值都是未定义行为。

2
“在 nullptr 上进行指针算术运算是否安全?”
不,对于 nullptr 进行算术运算是没有定义的,因为它本身不是一个指针类型(但存在将所有指针类型转换为 NULL 值的转换)。std::nullptr_t 是空指针字面值 nullptr 的类型。它是一个独立的类型,既不是指针类型也不是成员指针类型。请参见此处
总的来说,任意指针算术运算(即使在 NULL 值上)几乎肯定会引起问题 - 你没有分配那块内存 - 它不属于你尝试读取或写入的范围。
用于比较(例如超过结尾),可以正常使用,但否则您所编写的代码将导致未定义行为。
如需进一步了解,请参阅维基百科关于未定义行为的文章。

我不确定你第二段的逻辑:指针算术运算不会导致读取或写入内存。内存与 OP 的问题无关。 - Kerrek SB
那是关于任意部分的更多内容 - 是与OP无关的次要问题。我会整理一下。 - Niall

1
不,将偏移量添加到nullptr上不会导致nullptr。这是未定义的行为。

6
你说得对,添加非零偏移量不会导致 nullptr。然而,将零加/减到/从空指针是被定义好的,并且会导致一个空指针(这是 C 和 C++ 之间的一个具体区别 - 在 C 中这是未定义的)。 - Peter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接