不评估应用了 sizeof 的表达式是不是使得在 C++ 中取消引用 sizeof 内的空指针或无效指针是合法的?

Posted

技术标签:

【中文标题】不评估应用了 sizeof 的表达式是不是使得在 C++ 中取消引用 sizeof 内的空指针或无效指针是合法的?【英文标题】:Does not evaluating the expression to which sizeof is applied make it legal to dereference a null or invalid pointer inside sizeof in C++?不评估应用了 sizeof 的表达式是否使得在 C++ 中取消引用 sizeof 内的空指针或无效指针是合法的? 【发布时间】:2015-04-27 03:38:20 【问题描述】:

首先,我看到this question about C99 和接受的答案引用操作数未评估 C99 标准草案中的措辞。我不确定这个答案是否适用于 C++03。还有 this question about C++ 有一个接受的答案,引用了类似的措辞,而且 在某些情况下,会出现未计算的操作数。未计算的操作数不会被计算。 措辞。

我有这个代码:

 int* ptr = 0;
 void* buffer = malloc( 10 * sizeof( *ptr ) );

问题是 - sizeof() 内部是否存在空指针取消引用(以及 UB)?

C++03 5.3.3/1 说 sizeof 运算符在其操作数的对象表示中产生字节数。操作数要么是一个未计算的表达式,要么是一个带括号的类型 ID。

链接到答案引用这个或类似的措辞,并利用“未评估”部分来推断没有 UB。

但是,在这种情况下,我无法找到标准将 评估 与拥有或不拥有 UB 的确切位置联系起来。

“不评估”应用了 sizeof 的表达式是否使得在 C++ 中取消引用 sizeof 内的 null 或无效指针是合法的?

【问题讨论】:

@ParkYoung-Bae 这并没有多大帮助。 @juanchopanza 我想知道这些问题在哪里有我的答案吗?谢谢。 @ParkYoung-Bae 认真的吗?它引用了以下内容:“在某些情况下,会出现未计算的操作数。未计算的操作数不会被计算。” 我重新打开了,但我不确定标准是否必须明确说明未评估的操作数不会导致 UB。 IMVHO 行为(无论是否定义)发生在执行过程中,因此只能由执行语句或评估表达式引起。如果不计算表达式,则表达式不会导致任何行为,例如 if(0) int*p; *p = 0; 【参考方案1】:

规范只说取消引用一些为 NULL 的指针是 UB。由于 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 轻松想出类似的东西。

【讨论】:

我真正怀念的是支持这种“不做任何事情”声明的标准措辞。 @sharptooth 这不是“未评估”的意思吗? @juanchopanza 我不知道,我在标准中找不到答案,这就是我首先提出这个问题的原因。 那么,您是怀疑我所说的是否正确,还是只是怀疑标准在某处说明了这一点?或者以某种方式认为“也许,在某个地方,是一个编译器,它生成代码并访问传递给sizeof的东西的内容”? @MatsPetersson 我根本找不到标准中的措辞。是的,您的说法听起来很合理,但它们只是您的意见,除非它们得到标准措辞的支持。【参考方案2】:

由于您明确要求提供标准参考 - [expr.sizeof]/1:

操作数是一个表达式,它是一个未计算的操作数 (第 5 条),或带括号的 type-id

[expr]/8:

在某些情况下,未计算的操作数会出现(5.2.8、5.3.3、5.3.7、 7.1.6.2)。未计算的操作数不会被计算。

因为从不评估表达式(即取消引用),所以该表达式不受它通常会违反的某些约束的约束。仅检查类型。事实上,该标准在 [dcl.fct]/12 中的一个示例中使用了 null 引用本身:

trailing-return-type 对于一个更有用的类型是最有用的 复杂的指定之前 declarator-id

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

而不是

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

——结束注释 ]

【讨论】:

我在我的问题中引用了以上所有内容。 “评估”和导致行为之间缺少联系。 谢谢。它看起来很合理,尽管我同意尖牙,它错过了推理的一步。标准的作者可能很明显,以至于他们没有费心把它拼出来。 @hvd 好吧,我实际上只是跳到第一个答案,然后直接开始编写自己的答案——我误解了提问者想要证明的确切内容(也许我应该开始更彻底地阅读问题)。而且我相信你不会找到一个句子真正证实未经评估的代码可以在不引起 UB 的情况下做某些事情。 如果未计算的操作数“导致行为”,那么 decltype(t + u) 将导致 UB,因为 tu 尚未初始化...【参考方案3】:

我认为这在标准中目前未得到充分说明,就像What is the value category of the operands of C++ operators when unspecified? 等许多问题一样。我不认为这是故意的,就像 hvd 指出的那样,这对委员会来说可能是显而易见的。

在这个具体案例中,我认为我们有证据表明意图是什么。来自GB 91 comment from the Rapperswil meeting 说:

取消引用空指针作为我们规范的一部分有点令人反感,因为我们处于未定义行为的边缘。添加了 declval 函数模板,已经在这些相同的表达式中使用,这不再是必需的。

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

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

该建议被拒绝,因为它没有调用未定义的行为,因为它未评估:

没有未定义的行为,因为表达式是未计算的操作数。目前还不清楚提议的更改是否会更清晰。

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

我说未指定,但我想知道这是否包含在 4.1 [conv.lval] 部分中,它说:

左值指示的对象中包含的值是右值结果。当发生左值到右值的转换时 在 sizeof (5.3.3) 的操作数中,不访问包含在引用对象中的值,因为该运算符 不计算其操作数。

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

换句话说,只有“获取”、左值到右值转换的行为才会触发格式错误或未定义的行为

这有点推测,因为问题尚未解决。

【讨论】:

以上是关于不评估应用了 sizeof 的表达式是不是使得在 C++ 中取消引用 sizeof 内的空指针或无效指针是合法的?的主要内容,如果未能解决你的问题,请参考以下文章

具有可变长度数组类型的 Sizeof 运算符

sizeof 是在编译时还是运行时评估?

内联静态自动的初始化程序“sizeof(T)”...是不是需要实例化?

为啥 sizeof 表达式不是像 2、4、8 等这样的编译时常量?

多目标优化在推荐中的应用

为啥零长度 VLA 是 UB?