当指向无效内存时,sizeof(*ptr)是否为未定义行为?

39

我们都知道对空指针或未分配内存的指针进行解引用会导致未定义行为。

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

例如:

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

这个也是未定义的吗?


2
不同的编程语言有不同的规则。直到你只剩下一种语言标签,才能确定“规则”。 - n. m.
6
接受的答案提供了不是一个定义规范,而是定义规范。 - n. m.
7
我不确定你认为我的回答还需要添加什么。ISO标准非常明确地定义了sizeof的正确行为,没有任何变化的余地。你提出的另一个问题被关闭并重定向到这个问题,是由于C++某个特定实现中的一个错误所致,并不能减少标准的明确保证。如果你想引用标准中能够使我的答案失效的内容,请尽管这样做,我会根据需要进行更新。 - paxdiablo
1
@YSC 你有没有注意到paxdiablo的答案?它明确提供了标准引用“不被评估”。 - M.M
我做了:https://stackoverflow.com/q/51597864/5470596 - YSC
显示剩余3条评论
6个回答

51
通常情况下,您会发现sizeof(*x)实际上根本不会评估* x 。由于指针的解引用调用未定义的行为,而它是解引用指针的评估,因此您会发现这基本上没问题。C11标准在 6.5.3.4. sizeof运算符/2 (在所有这些引用中都强调了这一点)中有如下规定:
  

sizeof 运算符产生其操作数的大小(以字节为单位),其可以是表达式或类型括号中的名称。大小是从操作数的类型确定的。结果是整数。如果操作数的类型是可变长度数组类型,则对操作数进行评估;否则,不评估操作数,结果是整数常量。

这与C99中同一部分的措辞完全相同。当然,由于那时没有VLAs,因此C89的措辞略有不同。从3.3.3.4. sizeof运算符:
  

sizeof 运算符产生其操作数的大小(以字节为单位),其可以是表达式或类型括号中的名称。大小是从操作数的类型本身而不是被求值的类型确定的。结果是整数常量。

因此,在C中,对于所有非-VLA,不会进行解引用,语句是定义良好的。如果*x的类型是VLA,则被视为执行阶段sizeof,这需要在代码运行时进行处理-所有其他情况可以在编译时计算得出。如果x本身是VLA,则与其他情况相同,在使用*x作为sizeof()的参数时不会进行评估。
C ++具有(如预期的那样,因为它是一种不同的语言)稍有不同的规则,如标准的各个版本所示:
首先,C++03 5.3.3. Sizeof /1
  

sizeof 运算符产生其操作数的对象表示中的字节数。操作数可以是表达式,不被评估,或者是括号中的类型标识符。

C++11 5.3.3. Sizeof /1中,您将找到略有不同的措辞但相同的效果:
  

sizeof 运算符产生其操作数的对象表示中的字节数。操作数是表达式,是一个未评估的操作数(第5条),或者是括号中的类型标识符。

C++11 5.表达式/7(上述第5条款)将“未评估的操作数”定义为我最近看过的最无用,最冗余的短语之一,但我不知道ISO人写这篇文章时在想什么:
  

在某些情况下( [一些引用到详细说明这些上下文的部分-Pax]),出现了未评估的操作数。 未评估的操作数不会被评估。

C ++14/17具有与C ++11相同的措辞,但不一定在相同的部分,因为在相关部
#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 表达式的 类型 才是重要的,而该类型最终归结为变量 x 的类型。

除了第一行和最后一行可能有不同的指针大小之外,你看到的是:

42
4
4
4
4
42

3
为了完整性,在 C++ 中(它没有 VLAs),该表达式永远不会被计算:C++11,5.3.3 表示:“操作数要么是一个表达式,这是一个未计算的操作数(第 5 条款),或者是带括号的类型标识符。” - Mike Seymour
谢谢,@Mike,为了完整性将其纳入答案中。 - paxdiablo
2
@paxdiablo:明确指出解引用空指针是有效的C++代码可能很重要。当评估时,它会调用UB,但它可以编译得很好。YSC可能不知道这一点。 - Nicol Bolas
*x未被评估时,为什么不会导致UB? - xskxzr
2
@xskxzr 同样的原因是当未被评估时 2+2 不会得出 4。老实说,有些人似乎完全忽略了“未被评估”的这几个词。 - M.M
@xskxzr,看一下我在结尾添加的例子。sizeof 运算符中使用的所有表达式都不会被计算,只有它们的类型才是重要的。 - paxdiablo

17

不,sizeof是一个运算符,作用于类型而不是实际值(该值不会被计算)。

为了提醒您它是一个运算符,建议您在实际中省略括号。

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

2
那个表达式从未被计算 - 它只是被解析以确定结果的类型。就我所知,&*ptr 也是合法的。 - Chris Lutz
@ChrisLutz:谢谢,我已经将“未评估”纳入我的答案中。 - C. K. Young
为什么记住它是一个运算符很重要? - UncleBens

6
答案可能对于C语言来说有所不同,因为sizeof并不一定是一个编译时的结构,但在C ++中提供给sizeof的表达式从未被评估。因此,不存在未定义行为的可能性。由于类似的逻辑,您也可以“调用”从未定义的函数[因为实际上从未调用该函数,所以不需要定义],这在SFINAE规则中经常使用。

5

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


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

ptr是一个指向整数的指针。*ptr是一个整数。sizeof(*ptr)是sizeof(一个整数)。 - EricS

1

由于sizeof不评估其操作数(除非您使用C99或更高版本的可变长度数组),因此在表达式sizeof(* ptr)中,ptr未被评估,因此它未被取消引用。 sizeof运算符只需要确定表达式* ptr的类型以获取适当的大小。


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