考虑以下代码:
#include <stdio.h>
int *f(int (*p)[2])
{
return *p; //Possible UB here?
}
int main()
{
printf("%p", f(NULL));
}
我们是否将间接应用于空指针创建UB?
也许它不会因为数组类型的左值被转换回指针而实际上没有访问对象值。在这种情况下哪一个是真的?
编辑:我确切知道UB是什么。我只想要使用标准文件的证据或某种解释为什么或为什么不是上面的代码UB。答案 0 :(得分:3)
正如我在评论中所说,是的。任何取消引用NULL指针都会产生未定义的行为。
你必须意识到的是,未定义的行为意味着标准不会对结果发生任何要求或约束。
这意味着当代码的行为未定义时,实现可以像您描述的那样自由行动 - 或者不行。
编译器的行为与决定未定义的内容和不定义的内容无关。
答案 1 :(得分:0)
NULL
是一个空指针常量,并且尝试取消引用(ny)空指针(无效内存)将导致UB。
因此,理论上,我们不能取消引用包含NULL
的任何指针。
此处,p
是指针,p == NULL
,*p
是取消引用的尝试。因此,它会调用undefined behavior。
FWIW,NULL
的主要用例之一是提供有效值来检查并停止取消引用NULL
的指针。
答案 2 :(得分:0)
原始答案留在下面,因为我认为它对标准有很好的参考。
首先简短回答:很多人认为它显然是UB,即使我认为意图很清楚,我也无法在标准中找到显示表达式的参考是允许的。因此,每个标准都没有定义行为。
但是如下所述,取消引用指向数组的指针相当于将指针强制转换为数组的第一个元素。演员阵容完全由标准定义,因为如果指针指向真正的数组,则数组地址的位置是数组的第一个元素。如果指针为null,则明确允许将类型的空指针强制转换为指向另一种类型的空指针。所以只需替换
行return *p;
因为标准没有明确说明应该发生什么:
return (int *) p; // no UB here even if p is null!
这可以用于指向任何类型的数组的指针,包括多维数组:可以通过强制替换取消引用直接替换到底层子数组。
这是一个有趣的角落案例。恕我直言,该标准尚不清楚它是否不是未定义的行为。这里有一些提示,可以说它是,从草案n1256为C99或n1570为C11,6.5.3.2地址和间接操作员(所有强调都是我的):
§4一元*运算符表示间接...如果操作数的类型为''指向类型'',则结果的类型为''type''。 如果是 已经为指针分配了无效值,一元*运算符的行为是 未定义强>
关于该部分的说明坚持:
由unary *运算符解除引用指针的无效值是空指针...
但它不是很清楚,因为数组是一个派生类型,它是一个不可修改的左值,只能在两个上下文中使用:
它可以转换(衰变)到其基础类型的指针
它可以与[]
后缀运算符一起使用,为其元素之一构建左值
使用*p[i]
肯定是UB,因为我们首先在空指针上进行算术运算然后取消引用结果。毫无疑问,这里
但是在显示的代码(return *p;
)中,我们处于第一个上下文中,这意味着我们只将数组转换为指针。同一个注释(在同一段落上)说:
因此,&amp; * E相当于E(即使E是空指针)......
由于p
是指向数组的指针,因此应该应用多维数组的语义。
6.5.2.1段相同标准的数组下标是明确的多维数组:
§3连续的下标运算符指定多维数组对象的元素。 如果E是维度为i'j'的n维数组(n³2)。 。 。 'k,然后E(用作 除了左值之外)转换为指向(n - 1)维数组的指针 尺寸j'。 。 。 'k。如果unary *运算符明确地应用于此指针,或 隐式地,作为下标的结果,结果是指向(n - 1)维数组
恕我直言,这明确指出*p
是 (int *) p
所以函数f
在收到空指针时需要返回空指针。
但是这里引用的第一条评论让我们认为应用于空指针的任何*运算符都会导致UB。同一评论的第二部分证明它是错误的,但评论不是规范性的。因此,为了避免被未来版本的优化编译器主动追逐可能的UB而被烧毁,我会将其视为UB并且永远不会在实际代码中使用它,即使我真的认为它是允许的。
注意:我知道评论不是规范性的,但它们有助于理解标准。因此,当一条评论明确表示 &*E
等同于E
时(即使E
是空指针)它真的意味着只要结果仍然使用对于其地址,将operator *应用于空指针不一定是UB。