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

发布于 2024-12-09 10:24:24 字数 187 浏览 1 评论 0原文

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

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

例如:

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

这也是未定义的吗?

We all know that dereferencing an null pointer or a pointer to unallocated memory invokes undefined behaviour.

But what is the rule when used within an expression passed to sizeof?

For example:

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

Is this also undefined?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

少年亿悲伤 2024-12-16 10:24:24

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

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

这与 C99 中的同一部分的措辞相同。 C89 的措辞略有不同,因为当时还没有 VLA。来自 3.3.3.4。 sizeof 运算符

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

因此,在 C 中,对于所有非 VLA,不会发生取消引用,并且语句定义良好。如果 *x 的类型 VLA,则被视为执行阶段 sizeof,需要在代码执行时计算出来。 running - 所有其他都可以在编译时计算。如果x本身就是VLA,则与其他情况相同,当使用*x作为sizeof()。


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

首先,C++03 5.3.3。 /1 的大小:

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

在 C++11 5.3.3 中。 Sizeof /1,你会发现措辞略有不同,但效果相同:

sizeof 运算符生成其操作数的对象表示形式中的字节数。操作数可以是一个表达式,即一个未计算的操作数(第 5 条),也可以是一个带括号的类型 ID。

C++11 5. 表达式/7(上面提到的第 5 条)将术语“未求值的操作数”定义为可能是我读过一段时间以来最无用、最冗余的短语之一,但我不知道ISO人写的时候脑子里在想什么:

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

C++14/17 与 C++11 具有相同的措辞,但不一定位于相同的部分,因为在相关部分。它们位于 5.3.3 中。大小为 /15。 C++14 和 8.3.3 的表达式 /8。大小为 /1 和 8。 C++17 的表达式 /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 截然不同的内容(< code>774,根据我的粗略计算),因为x已经改变了很多。但实际上情况并非如此,因为这里重要的只是 sizeof 中表达式的类型,并且该类型归结为任何类型 x 是。

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

42
4
4
4
4
42

In most cases, you will find that sizeof(*x) does not actually evaluate *x at all. And, since it's the evaluation (de-referencing) of a pointer that invokes undefined behaviour, you'll find it's mostly okay. The C11 standard has this to say in 6.5.3.4. The sizeof operator /2 (my emphasis in all these quotes):

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand. The result is an integer. If the type of the operand is a variable length array type, the operand is evaluated; otherwise, the operand is not evaluated and the result is an integer constant.

This is identical wording to the same section in C99. C89 had slightly different wording because, of course, there were no VLAs at that point. From 3.3.3.4. The sizeof operator:

The sizeof operator yields the size (in bytes) of its operand, which may be an expression or the parenthesized name of a type. The size is determined from the type of the operand, which is not itself evaluated. The result is an integer constant.

So, in C, for all non-VLAs, no dereferencing takes place and the statement is well defined. If the type of *x is a VLA, that's considered an execution-phase sizeof, something that needs to be worked out while the code is running - all others can be calculated at compile time. If x itself is the VLA, it's the same as the other cases, no evaluation takes place when using *x as an argument to sizeof().


C++ has (as expected, since it's a different language) slightly different rules, as shown in the various iterations of the standard:

First, C++03 5.3.3. Sizeof /1:

The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is not evaluated, or a parenthesized type-id.

In, C++11 5.3.3. Sizeof /1, you'll find slightly different wording but the same effect:

The sizeof operator yields the number of bytes in the object representation of its operand. The operand is either an expression, which is an unevaluated operand (Clause 5), or a parenthesized type-id.

C++11 5. Expressions /7 (the above mentioned clause 5) defines the term "unevaluated operand" as perhaps one of the most useless, redundant phrases I've read for a while, but I don't know what was going through the mind of the ISO people when they wrote it:

In some contexts ([some references to sections detailing those contexts - pax]), unevaluated operands appear. An unevaluated operand is not evaluated.

C++14/17 have the same wording as C++11 but not necessarily in the same sections, as stuff was added before the relevant parts. They're in 5.3.3. Sizeof /1 and 5. Expressions /8 for C++14 and 8.3.3. Sizeof /1 and 8. Expressions /8 for C++17.

So, in C++, evaluation of *x in sizeof(*x) never takes place, so it's well defined, provided you follow all the other rules like providing a complete type, for example. But, the bottom line is that no dereferencing is done, which means it does not cause a problem.

You can actually see this non-evaluation in the following program:

#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';
}

You might think that the final line would output something vastly different to 42 (774, based on my rough calculations) because x has been changed quite a bit. But that is not actually the case since it's only the type of the expression in sizeof that matters here, and the type boils down to whatever type x is.

What you do see (other than the possibility of different pointer sizes on lines other than the first and last) is:

42
4
4
4
4
42
撩动你心 2024-12-16 10:24:24

不。sizeof 是一个运算符,适用于类型,而不是实际值(不进行求值)。

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

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

No. sizeof is an operator, and works on types, not the actual value (which is not evaluated).

To remind you that it's an operator, I suggest you get in the habit of omitting the brackets where practical.

int* ptr = 0;
size_t size = sizeof *ptr;
size = sizeof (int);   /* brackets still required when naming a type */
橘亓 2024-12-16 10:24:24

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

The answer may well be different for C, where sizeof is not necessarily a compile-time construct, but in C++ the expression provided to sizeof is never evaluated. As such, there is never a possibility for undefined behavior to exhibit itself. By similar logic, you can also "call" functions that are never defined [because the function is never actually called, no definition is necessary], a fact that is frequently used in SFINAE rules.

梦行七里 2024-12-16 10:24:24

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

sizeof and decltype do not evaluate their operands, computing types only.

や莫失莫忘 2024-12-16 10:24:24

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

sizeof(*ptr) is the same as sizeof(int) in this case.

迷迭香的记忆 2024-12-16 10:24:24

由于 sizeof 不计算其操作数(除非您使用的是 C99 或更高版本,否则在可变长度数组的情况下),因此在表达式 sizeof (*ptr) 中,不计算 ptr,因此它是没有解除引用。 sizeof运算符只需要确定表达式*ptr的类型即可获得合适的大小。

Since sizeof does not evaluate its operand (except in the case of variable length arrays if you're using C99 or later), in the expression sizeof (*ptr), ptr is not evaluated, therefore it is not dereferenced. The sizeof operator only needs to determine the type of the expression *ptr to get the appropriate size.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文