在 C++ 中,不对 sizeof 应用的表达式进行评估是否意味着可以在 sizeof 中取消引用空指针或无效指针吗?

15

首先,我看到了关于C99的这个问题,并且被接受的答案引用了C99标准草案中的“操作数未计算”措辞。我不确定这个答案适用于C++03。还有一个关于C++的问题,其被接受的答案引用了类似的措辞,并且还包括“在某些情况下,未计算的操作数会出现。未计算的操作数未被计算。”的措辞。

我有这段代码:

 int* ptr = 0;
 void* buffer = malloc( 10 * sizeof( *ptr ) );
问题是:在 sizeof() 内是否存在空指针取消引用(null pointer dereference)(因此 UB)?
根据 C++03 5.3.3/1 的规定,sizeof 运算符产生其操作数对象的对象表示中的字节数。操作数可以是未评估的表达式或带括号的类型标识符。 所链接的答案引用了这个或类似的措辞,并利用“未被评估”的部分推导出没有 UB。
然而,我找不到标准在这种情况下将 评估 和具有或不具有 UB 相关联的确切位置。
在 C++ 中,在 sizeof 中使用“未评估”的表达式是否合法,能否取消引用 null 或无效指针?

@ParkYoung-Bae 这并没有太大帮助。 - sharptooth
@juanchopanza,我可以问一下这个问题的答案是否与我的问题相关吗?谢谢。 - sharptooth
1
我重新打开了,但我不确定标准是否必须明确说明未评估的操作数不会导致UB。 - juanchopanza
1
在我看来,行为(无论是否定义)发生在执行期间,因此只能由执行语句或评估表达式引起。如果未评估表达式,则表达式无法引起任何行为,例如 if(0) { int*p; *p = 0;} - CiaPan
@KeithThompson 主观差异在于,在 if 示例中,该语句根本无法到达,而在 sizeof 示例中,它不一定是无法到达的。 - sharptooth
显示剩余10条评论
3个回答

11

我认为这在标准中目前还没有具体规定,就像许多问题一样,比如当未指定时,C++运算符的操作数值类别是什么?。我不认为这是有意为之的,正如hvd所指出的,委员会可能认为这很明显。

在这种特定情况下,我认为我们有证据显示出其意图。来自瑞士拉珀斯维尔会议的GB 91评论,它说:

将空指针解除引用作为我们规范的一部分,虽然我们正在玩弄未定义行为的边缘,但这是令人愉快的。通过添加declval函数模板(已在这些相同表达式中使用),这就不再是必需的。

并提出了替代表达式,它指的是这个不再是标准中的表达式,但可以在N3090中找到。

noexcept(*(U*)0 = declval<U>())

该建议被拒绝,因为这并没有调用未定义行为,因为它是未求值的:

 

由于表达式是未求值的操作数,因此不存在未定义行为。提议的更改是否更清晰并不明确。

这个理由也适用于 sizeof,因为它的操作数是未求值的。

我说不够明确,但我想知道这是否在第 4.1 节中有所涵盖 [conv.lval],其中写道:

 

lvalue 指向的对象中包含的值是 rvalue 结果。当 sizeof(5.3.3) 的操作数中发生 lvalue-to-rvalue 转换时,由于该运算符不评估其操作数,因此不访问所引用对象中包含的值。

它说未访问所包含的值,如果我们遵循问题232的逻辑推断,这意味着没有未定义行为:

 

换句话说,只有“获取”lvalue-to-rvalue转换才会触发不良形成或未定义的行为

这有些推测性,因为该问题尚未解决。


6

既然你明确要求标准参考 - [expr.sizeof]/1:

操作数可以是表达式,该表达式是未评估的操作数(5章),或括号内的类型标识符

[expr]/8:

在某些上下文中,未评估的操作数出现(5.2.8, 5.3.3, 5.3.7, 7.1.6.2)。未评估的操作数不会被评估。

由于表达式(即取消引用)从未被评估,因此该表达式不受通常会违反的一些约束的约束。仅检查类型。实际上,标准本身在[dcl.fct]/12的示例中使用空引用:

A trailing-return-type is most useful for a type that would be more complicated to specify before the declarator-id:

template <class T, class U> auto add(T t, U u) -> decltype(t + u);

rather than

template <class T, class U> decltype((*(T*)0) + (*(U*)0)) add(T t, U u);

— end note ]


我在我的问题中引用了上述所有内容。 "评估"和导致行为之间存在缺失的链接。 - sharptooth
谢谢。看起来很合理,尽管我同意 sharptooth 的观点,认为它在推理过程中缺少了一步。可能对于标准的作者来说,这是如此显而易见,以至于他们没有费心详细说明。 - user743382
@hvd 嗯,实际上我只是跳到了第一个答案,然后直接开始写我的答案 - 我误解了提问者想要证明的内容(也许我应该更仔细地阅读问题)。而且我确信你不会找到一句话实际上确认未评估的代码可以做某些事情而不引起 UB。 - Columbo
如果未评估的操作数“引起行为”,那么decltype(t + u)会导致UB,因为tu尚未初始化... - M.M

4
规范仅表示解引用某些空指针是未定义的。由于 sizeof() 不是真正的函数,它实际上只使用参数获取类型而不会引用指针。这就是为什么它能够工作的原因。其他人可以查找规范措辞,说明“sizeof 的参数不会被引用”以获得所有分数。
请注意,执行以下操作也是完全合法的: int arr[2]; size_t s = sizeof(arr[-111100000]); - 无论索引是什么,都没有关系,因为 sizeof 实际上从未对传递的参数“执行任何操作”。
另一个示例,以展示它的“不执行任何操作”的方式如下:
int func()
{
    int *ptr = reinterpret_cast<int*>(32);
    *ptr = 7;
    return 42;
}

size_t size = sizeof(func()); 

再次强调,这不会崩溃,因为func()只是由编译器解析为它产生的类型。

同样,如果sizeof实际上对参数进行了“操作”,那么当你这样做时会发生什么:

   char *buffer = new sizeof(char[10000000000]);

它会创建一个 10000000000 的堆栈分配,然后在代码崩溃时返回大小,因为没有足够的兆字节的堆栈?[在某些系统中,堆栈大小以字节计算,而不是兆字节]。虽然没有人编写这样的代码,但您可以轻松地使用 typedefbuffer_type 定义为 char 数组,或者一些具有大容量的 struct 来设计类似的代码。

1
我真正想表达的是标准措辞支持这种“不执行任何操作”的说法。 - sharptooth
@juanchopanza 我不知道,在标准中也找不到答案,这就是我首先提出问题的原因。 - sharptooth
那么,你是在怀疑我的话是否正确,还是在怀疑标准是否有所规定?或者你认为“也许,在某个地方,有一种编译器可以生成代码并访问传递到 sizeof 中的内容”? - Mats Petersson
@MatsPetersson 我根本找不到标准文件中的措辞。是的,你的声明听起来很有道理,但如果没有标准文件的支持,那就只是你的个人意见而已。 - sharptooth
好的,那就投票支持上面引用规范的答案吧,因为它明确表示“未评估” - 我的理解是“没有从表达式本身生成代码”,因为这将导致完全不可能的情况。 - Mats Petersson
显示剩余9条评论

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