是指向无效内存时sizeof(*ptr)未定义的行为

Is sizeof(*ptr) undefined behavior when pointing to invalid memory?

本文关键字:ptr 未定义 无效 内存 sizeof      更新时间:2023-10-16

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

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

例如:

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

这也是未定义的吗?

在大多数情况下,您会发现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中表达式的类型,并且该类型可以归结为x的任何类型

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

42
4
4
4
4
42

编号。sizeof是一个运算符,它处理类型,而不是实际值(未计算(。

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

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

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

sizeofdecltype不计算其操作数,仅计算类型。

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

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