不评估应用了 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++?

首先,我看到 this question about C99 and the accepted answer references operand is not evaluated wording in the C99 Standard draft. I'm not sure this answer applies to C++03. There's also this question about C++ 有一个接受的答案,引用了类似的措辞,而且 在某些情况下,会出现未计算的操作数。未求值的操作数不求值。 措辞。

我有这个代码:

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

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

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

链接到答案引用这个或类似的措辞并利用 "is not evaluated" 部分来推断没有 UB。

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

"not evaluating" 应用了 sizeof 的表达式是否允许在 C++ 中取消引用 sizeof 内的空指针或无效指针?

规范只说取消引用某些为 NULL 的指针是 UB。由于 sizeof() 不是真正的函数,并且除了获取类型之外它实际上不使用参数做任何事情,所以它从不引用指针。这就是为什么它有效。其他人可以通过查找说明 "the argument to sizeof doesn't get referenced" 的规范措辞获得所有积分。

请注意,int arr[2]; size_t s = sizeof(arr[-111100000]); 也是完全合法的 - 索引是什么并不重要,因为 sizeof 实际上从未 "does anything" 传递给参数。

另一个展示它如何 "not doing anything" 的例子是这样的:

int func()
{
    int *ptr = reinterpret_cast<int*>(32);
    *ptr = 7;
    return 42;
}

size_t size = sizeof(func()); 

同样,这不会崩溃,因为 func() 只是由编译器解析为它生成的类型。

同样,如果 sizeof 实际上 "does something" 带有参数,当你这样做时会发生什么:

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

是否会创建一个 10000000000 堆栈分配,然后在代码崩溃后返回大小,因为没有足够的兆字节堆栈? [在某些系统中,堆栈大小以字节计算,而不是兆字节]。虽然没有人写过这样的代码,但您可以使用 buffer_typetypedef 作为 char 的数组或某种 struct 的数组来轻松想出类似的东西内容。

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

The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id.

[表达式]/8:

In some contexts, unevaluated operands appear (5.2.8, 5.3.3, 5.3.7, 7.1.6.2). An unevaluated operand is not evaluated.

因为从未对表达式(即取消引用)求值,所以该表达式不受通常会违反的某些约束的约束。仅检查类型。事实上,该标准在 [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 ]

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

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

It is mildly distasteful to dereference a null pointer as part of our specification, as we are playing on the edges of undefined behaviour. With the addition of the declval function template, already used in these same expressions, this is no longer necessary.

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

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

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

There is no undefined behavior because the expression is an unevaluated operand. It's not at all clear that the proposed change would be clearer.

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

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

The value contained in the object indicated by the lvalue is the rvalue result. When an lvalue-to-rvalue conversion occurs within the operand of sizeof (5.3.3) the value contained in the referenced object is not accessed, since that operator does not evaluate its operand.

它表示包含的值未被访问,如果我们按照issue 232的逻辑表示没有未定义的行为:

In other words, it is only the act of "fetching", of lvalue-to-rvalue conversion, that triggers the ill-formed or undefined behavior

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