【问题标题】:Is sizeof(*ptr) undefined behavior when pointing to invalid memory?指向无效内存时 sizeof(*ptr) 的行为是否未定义?
【发布时间】:2011-12-04 23:47:26
【问题描述】:

我们都知道取消引用空指针或指向未分配内存的指针会调用未定义的行为。

但是在传递给sizeof 的表达式中使用时的规则是什么?

例如:

int *ptr = 0;
int size = sizeof(*ptr);

这也是未定义的吗?

【问题讨论】:

  • 不同的语言有不同的规则。在您删除除一个语言标签之外的所有标签之前,没有“规则”。
  • @n.m.这可能是现在“已经完成的事情”的情况。我不认为在这里添加针对不同 C++ 标准量身定制的另一个答案会造成任何伤害。
  • @YSC 接受的答案提供的不是 a 而是 the 最终规范。
  • YSC,我不确定您认为我的答案中还可以添加什么。 ISO 标准在定义sizeof 的正确行为方面非常 明确,没有 变化范围。由于 C++ 的一个特定 实现 中的错误,您的另一个问题作为这个问题的欺骗而被关闭是必要的,这一事实绝不会降低标准的明确保证。如果您想在 standard 中引用一些您认为会使我的答案无效的内容,请务必这样做,如果需要,我会更新它。
  • @YSC 你是否忽略了paxdiablo 的回答?它专门提供了“未评估”的标准报价。

标签: c++ c language-lawyer


【解决方案1】:

在大多数情况下,您会发现 sizeof(*x) 实际上根本不计算 *x。而且,由于它是调用未定义行为的指针的求值(取消引用),您会发现它基本上没问题。 C11 标准在6.5.3.4. The sizeof operator /2 中有这样的说法(我在所有这些引用中强调):

sizeof 运算符产生其操作数的大小(以字节为单位),它可以是表达式或带括号的类型名称。大小由操作数的类型决定。结果是一个整数。如果操作数的类型是变长数组类型,则计算操作数;否则,不计算操作数,结果是一个整数常量。

这与 C99 中的同一部分的措辞相同。 C89 的措辞略有不同,因为当时当然没有 VLA。来自3.3.3.4. The sizeof operator

sizeof 运算符产生其操作数的大小(以字节为单位),它可以是表达式或带括号的类型名称。大小由操作数的类型决定,它本身不会被计算。结果是一个整数常量。

因此,在 C 中,对于所有非 VLA,都不会发生解除引用,并且语句定义明确。如果*x 的类型 VLA,这被认为是执行阶段sizeof,需要在代码运行时解决 - 所有其他都可以在编译时计算。如果x 本身是VLA,则与其他情况相同,使用*x 作为sizeof() 的参数时不会进行评估。


C++ 的规则(正如预期的那样,因为它是一种不同的语言)略有不同,如标准的各种迭代所示:

首先,C++03 5.3.3. Sizeof /1

sizeof 运算符在其操作数的对象表示中产生字节数。操作数可以是一个表达式,不计算,或带括号的类型 ID。

C++11 5.3.3. Sizeof /1,您会发现措辞略有不同,但效果相同:

sizeof 运算符在其操作数的对象表示中产生字节数。操作数要么是一个表达式,它是一个未计算的操作数(第 5 条),要么是一个带括号的类型 ID。

C++11 5. Expressions /7(上面提到的第 5 条)将术语“未评估的操作数”定义为我读过一段时间的最无用、最多余的短语之一,但我不知道脑子里在想什么ISO 人写的时候:

在某些上下文中([对详细说明这些上下文的部分的引用 - pax]),会出现未计算的操作数。 未计算的操作数不会被计算。

C++14/17 与 C++11 具有相同的措辞,但不一定在相同的部分中,因为在相关部分之前添加了内容。它们在 C++14 的 5.3.3. Sizeof /15. Expressions /8 以及 C++17 的 8.3.3. Sizeof /18. Expressions /8 中。

所以,在 C++ 中,sizeof(*x) 中的 *x 的评估 从不 发生,所以它的定义很好,只要您遵循所有其他规则,例如提供完整类型。但是,底线是没有进行取消引用,这意味着它不会导致问题。

您实际上可以在以下程序中看到这种非评估:

#include <iostream>
#include <cmath>

int main() {
    int x = 42;
    std::cout << x << '\n';

    std::cout << sizeof(x = 6) << '\n';
    std::cout << sizeof(x++) << '\n';
    std::cout << sizeof(x = 15 * x * x + 7 * x - 12) << '\n';
    std::cout << sizeof(x += sqrt(4.0)) << '\n';

    std::cout << x << '\n';
}

您可能认为最后一行会输出与42 大不相同的内容(774,基于我的粗略计算),因为x 已经发生了很大变化。但实际上并非如此,因为这里只有 sizeof 中表达式的 type 很重要,并且类型归结为 x 的任何类型。

看到的(除了第一行和最后一行之外的指针大小不同的可能性)是:

42
4
4
4
4
42

【讨论】:

  • 为了完整起见,在 C++(没有 VLA)中,从不计算表达式:C++11, 5.3.3 说“操作数是一个表达式,它是一个未计算的操作数 (第 5 条),或带括号的类型 ID。”
  • 干杯,@Mike,将其纳入完整的答案。
  • @paxdiablo:显式添加延迟空指针是有效的 C++ 代码可能很重要。它在评估时调用UB,但它编译就好了。 YSC 可能没有意识到这一点。
  • 为什么*x 在未评估时不会导致 UB?
  • @xskxzr 与2+2 在未评估时不会导致4 的原因相同。老实说,似乎有些人完全忽略了“未评估”这个词
【解决方案2】:

没有。 sizeof 是一个运算符,作用于类型,而不是实际值(未计算)。

为了提醒你这是一个运算符,我建议你养成在可行的情况下省略括号的习惯。

int* ptr = 0;
size_t size = sizeof *ptr;
size = sizeof (int);   /* brackets still required when naming a type */

【讨论】:

  • 那个,并且表达式永远不会被评估 - 它只是被解析以确定结果的类型。对于它的价值,我认为&amp;*ptr 也是合法的。
  • @ChrisLutz:谢谢,我已将“未评估”纳入我的回答中。
  • 为什么记住它是一个运算符很重要?
【解决方案3】:

对于 C,答案可能会有所不同,其中 sizeof 不一定是编译时构造,但在 C++ 中,提供给 sizeof 的表达式永远不会被计算。因此,未定义的行为永远不可能表现出来。通过类似的逻辑,你也可以“调用”从未定义过的函数[因为函数从未真正被调用过,因此不需要定义],这是 SFINAE 规则中经常使用的事实。

【讨论】:

    【解决方案4】:

    sizeofdecltype 不计算它们的操作数,只计算类型。

    【讨论】:

      【解决方案5】:

      sizeof(*ptr) 在这种情况下与sizeof(int) 相同。

      【讨论】:

      • ptr 是一个指向整数的指针。 *ptr 是一个整数。 sizeof(*ptr) 是 sizeof( 一个整数)。
      【解决方案6】:

      由于 sizeof 不计算其操作数(如果您使用 C99 或更高版本,则在可变长度数组的情况下除外),在表达式 sizeof (*ptr) 中,不会计算 ptr,因此不会取消引用。 sizeof 运算符只需要确定表达式*ptr 的类型即可得到合适的大小。

      【讨论】:

        猜你喜欢
        • 2011-04-19
        • 2010-12-22
        • 1970-01-01
        • 2017-01-27
        • 2019-03-08
        • 2019-05-21
        • 2021-11-11
        • 2018-11-05
        • 2021-12-28
        相关资源
        最近更新 更多