空指针是否间接指向数组类型 UB?

Is indirection of null pointer to array type UB?

考虑这段代码:

#include <stdio.h>

int *f(int (*p)[2])
{
    return *p; //Possible UB here?
}

int main()
{
    printf("%p", f(NULL));
}

我们正在对空指针应用间接寻址这一事实会创建 UB 吗?

也许不会,因为数组类型的左值被转换回指针并且实际上没有访问对象值。哪一个是真实的?

编辑:我很清楚 UB 是什么。我只想使用标准论文来证明或某种解释为什么或为什么不是上面的代码 UB。

NULL是一个空指针常量,尝试解引用一个(ny)空指针(无效内存)将导致UB。

因此,理论上,我们不能解引用任何包含 NULL 的指针。

这里,p是一个指针,而p == NULL*p是试图解引用。因此,它调用 undefined behavior.

FWIW,NULL 的主要用例之一是提供一个 有效 值来检查和停止对持有 NULL 的指针的取消引用.

正如我在评论中所说,是的。对 NULL 指针的任何取消引用都会产生未定义的行为。

您必须意识到,未定义的行为意味着标准对结果所发生的情况没有任何要求或限制。

这意味着当代码的行为未定义时,实现可以自由地按照您描述的方式行事,也可以不按照您的描述行事。 不需要以这种方式表现或不表现。

编译器的行为与决定什么是未定义什么不是未定义无关。

原始答案留在下面,因为我认为它对标准有有趣的参考。

首先是一个简短的回答:许多其他人认为它显然是 UB,即使我认为 intent 很清楚,我也找不到标准中的参考表明表达式 是允许的。因此,该行为未根据标准定义。

但如下所述,取消对数组指针的引用等同于将指针强制转换为数组的第一个元素。并且转换由标准完美定义,因为如果指针指向真正的数组,则位于数组地址的是数组的第一个元素。如果指针为空,则明确允许将指向一种类型的空指针转换为指向另一种类型的空指针。所以只需替换行

return *p;

因为标准没有明确规定应该发生什么:

return (int *) p; // no UB here even if p is null!

这可用于指向任何类型数组的指针,包括多维数组:取消引用可以安全地替换为强制转换为紧邻的底层子数组。


这是一个有趣的极端案例。恕我直言,该标准不清楚它是否属于未定义行为。这里有一些提示可以说是,来自 C99 草案 n1256 或 C11 草案 n1570,6.5.3.2 地址和间接运算符(所有强调都是我的):

§4 The unary * operator denotes indirection... If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an invalid value has been assigned to the pointer, the behavior of the unary * operator is undefined.

关于那部分的注释坚持认为:

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer...


但不是很清楚,因为数组是派生类型,是不可修改的左值,只能在两种情况下使用:

  • 它可以被转换(衰减)到它的底层类型的指针

  • 它可以与 [] 后缀运算符一起使用,为其元素之一构建左值

使用*p[i]肯定是UB,因为我们首先对空指针进行算术运算,然后取消引用结果。毫无疑问这里

但是在显示的代码(return *p;)中,我们处于第一个上下文中,这意味着我们只将数组转换为指针。同样的注释(在同一段)说:

Thus, &*E is equivalent to E (even if E is a null pointer)...

由于p是指向数组的指针,所以应该应用多维数组的语义。 相同标准的 6.5.2.1 数组下标段落明确说明了多维数组会发生什么:

§ 3 Successive subscript operators designate an element of a multidimensional array object. If E is an n-dimensional array (n ³ 2) with dimensions i ´ j ´ . . . ´ k, then E (used as other than an lvalue) is converted to a pointer to an (n - 1)-dimensional array with dimensions j ´ . . . ´ k. If the unary * operator is applied to this pointer explicitly, or implicitly as a result of subscripting, the result is the pointed-to (n - 1)-dimensional array

恕我直言,这清楚地表明 *p (int *) p 因此函数 f 需要 return 一个空指针当它收到一个空指针时。

但是这里引用的第一条评论让我们认为任何应用于空指针的 * 运算符都会导致 UB。同一评论的第二部分证明它是错误的,但评论不规范。因此,为了避免被未来版本的优化编译器积极追逐可能的 UB 所困扰,我会将其视为 UB,并且永远不会在实际代码中使用它,即使我真的认为它是允许的。

注意:我知道注释不是规范的,但它们是为了帮助理解标准。因此,当一条评论明确表示 &*E 等同于 E(即使 E 是一个空指针) 它实际上意味着提供了结果仍然用于其地址,将运算符 * 应用于空指针不一定是 UB。