首先简短回答:许多人认为这显然是未定义行为,即使我认为意图是清晰的,我也找不到标准中允许表达式的参考。因此,根据标准,行为是未定义的。
但是如下所述,将指向数组的指针解引用等同于将指针强制转换为数组的第一个元素。而且标准通过解释说明该强制转换完全被定义,因为如果指针指向真正的数组,则在数组地址处的内容就是数组的第一个元素。如果指针为空,则明确允许将空指针转换为另一类型的空指针。因此,只需替换该行:
return *p;
因为标准没有明确指定应该如何处理:
return (int *) p; // no UB here even if p is null!
这可以用于指向任何类型的数组,包括多维数组:解引用可以安全地替换为对立即底层子数组的转换。
这是一个有趣的边界情况。在我看来,标准并不清楚它是否属于未定义行为。以下是一些提示,可能表明它是从C99的草案n1256或C11的草案n1570中,6.5.3.2地址和间接运算符(所有强调都是我的):
§4一元*运算符表示间接寻址...如果操作数具有“指向类型”的类型,则结果具有“类型”类型。如果已将无效值分配给指针,则一元*运算符的行为是未定义的。
关于该部分的注释坚持认为:
通过一元*运算符对指针进行取消引用的无效值之一是空指针...
但这并不是很清楚,因为数组是一种派生类型,是一个不可修改的lvalue,并且只能在两个上下文中使用:
使用*p [i]
肯定会导致UB,因为我们首先在空指针上进行算术运算,然后解引用结果。这里毫无疑问
但在所示代码中(return *p;
),我们处于第一个上下文中,这意味着我们只将数组转换为指针。同一段落中的同一注释(on same paragraph)说:
因此,&*E等同于E(即使E是空指针)...
由于p
是指向数组的指针,因此应该应用多维数组的语义。同一标准的第6.5.2.1节“数组下标”明确说明了多维数组的情况:
§3 连续的下标运算符指定多维数组对象的一个元素。如果 E 是一个 n 维数组(n≥2),其维度为 i×j×...×k,则 E(用作非左值)被转换为一个指向具有维度 j×...×k 的 (n-1) 维数组的指针。如果对此指针显式地或隐式地应用一元 * 运算符,结果就是指向的 (n-1) 维数组。
在我看来,这清楚地说明了 *p 就是 (int *) p,因此当函数 f 收到空指针时,需要返回一个空指针。
但是这里引用的第一条评论让人认为任何应用于空指针的 * 运算符都会导致未定义行为。同一评论的第二部分证明了这是错误的,但是评论并不是规范性的。因此,为了避免被未来版本的优化编译器积极追踪可能的未定义行为而受到伤害,我会将其视为未定义行为,并且永远不会在实际代码中使用它,即使我真的认为它是允许的。
注意:我知道注释不是规范的一部分,但它们存在是为了帮助我们理解标准。所以当一个注释明确说&*E
等同于E
(即使E
是一个空指针)时,这实际上意味着只要结果仍然用于地址,对空指针应用运算符 * 不一定会导致未定义行为。