首页 > 解决方案 > C++ 是否保证一致的指针表示?

问题描述

当指针转换为其他指针类型时,C++ 是否保证一致的指针表示?

例如,C++ 是否保证以下程序的任何内容?

<stdint.h>

struct Foo {};
struct Bar : Foo {};

int main() {
    Bar obj;

    Foo * a = &obj;
    Bar * b = &obj;
    void *c = &obj;
#if MAYBE_UB
    int * d = reinterpret_cast<int *>(&obj);
#endif

    auto aa = reinterpret_cast<uintptr_t>(a);
    auto bb = reinterpret_cast<uintptr_t>(b);
    auto cc = reinterpret_cast<uintptr_t>(c);
#if MAYBE_UB
    auto dd = reinterpret_cast<uintptr_t>(d); // UB? Not reading the pointee...
#endif

    if (aa != bb) printf("bb differs\n");
    if (aa != cc) printf("cc differs\n");
#if MAYBE_UB
    if (aa != dd) printf("dd differs\n");
#endif

    return 0;
}

标签: c++c++20

解决方案


C++ 是否保证一致的指针表示?

不,指针可以用编译器想要的任何不一致的方式表示。一般来说,语言尽量抽象,尽可能少地谈论事物的表示。留给实现解决这个问题。

当指针转换为其他指针类型时,C++ 是否保证一致的指针表示?

不,指针可以以任何他们想要的方式表示。例如,使用石头和棍子。

C++ 是否保证以下程序的任何内容?

TL;DR 好吧,是的,代码应该编译(忽略#includfe顶部的缺失)。但是不能保证输出。

Aaanyway,我认为你对指针的表示不感兴趣,但是如果指针的在指针转换为不同的类型时发生变化。这与如何“表示”值无关,指针的表示可以随编译器的意愿而改变。好吧,好的,我们知道char *void *有相同的表示,来自https://eel.is/c++draft/basic.compound#5

指向 cv void 的指针可用于指向未知类型的对象。这样的指针应该能够保存任何对象指针。“指向 cv void 的指针”类型的对象应具有与“指向 cv char”类型的对象相同的表示和对齐要求。

无论如何,我们知道void *指针https://eel.is/c++draft/expr#conv.ptr

“指向 cv T 的指针”类型的纯右值,其中 T 是对象类型,可以转换为“指向 cv void 的指针”类型的纯右值。 此转换不会更改指针值。

这也是相关的:

“pointer to cv D”类型的纯右值,其中 D 是完整的类类型,可以转换为“pointer to cv B”类型的纯右值,其中 B 是 D 的基类([class.derived])。如果 B 是 D 的不可访问的 ([class.access]) 或不明确的 ([class.member.lookup]) 基类,则需要进行此转换的程序是非良构的。转换的结果是指向派生类对象的基类子对象的指针。

但是结果的价值没有任何意义。可以相同,也可以不同,没有具体说明。

问题出现了,您的代码使用uintptr_t. 我们知道它cstdint必须与C中的相同stdint.h,并且从 C 中我们知道C99 7.18.1.4

以下类型指定一个无符号整数类型,其属性是任何指向 void 的有效指针都可以转换为此类型,然后转换回指向 void 的指针,结果将与原始指针进行比较:

   uintptr_t

我们对 的价值一无所知(uintptr_t)(Bar*)&obj。我们也不知道两个uintptr_t变量是否比较相等,即使它们是从两个相等的void指针创建的。只有当您将它们转换回来时它们比较相等。我们回到 reinterpret_cast https://eel.is/c++draft/expr#reinterpret.cast-4的一般规则:

指针可以显式转换为任何大到足以容纳其类型的所有值的整数类型。映射函数是实现定义的。

[注2:对于那些知道底层机器的寻址结构的人来说,这并不奇怪。——尾注]

“映射功能”可以是任何东西。它可以产生依赖于任何东西的不一致的值——它是由实现定义的。所以,基本上,我们对结果值一无所知uintptr_t,除了那里应该有一些值。

无论如何,从上面我可以得出结论,代码中的所有 uintptr_t转换都会产生实现定义的值,并且可以具有实现希望它们具有的任何值。该程序已定义但实现定义的行为 - 这些ifs 中的任何一个都可以是真或假,具体取决于编译器使用的“指向整数映射函数的指针”。

然而,我们知道,任何理智的编译器都会有一个理智的“映射函数”,将指针映射到整数类型,并且这个映射函数“对于那些知道底层机器的寻址结构的人来说并不奇怪”,并且会产生一致的值。您可能还对这个指针出处感兴趣。

至于MAYBE_UB,里面没有什么未定义的,我们知道https://eel.is/c++draft/expr#reinterpret.cast-7

对象指针可以显式转换为不同类型的对象指针。当对象指针类型的纯右值v转换为对象指针类型“指向cv T的指针”时,结果为static_cast<cv T*>(static_cast<cv void*>(v))。

但我们从https://eel.is/c++draft/expr#static.cast-13得知:

“指向 cv1 void 的指针”类型的纯右值可以转换为“指向 cv2 T 的指针”类型的纯右值,其中 T 是对象类型,而 cv2 与 cv1 具有相同的 cv 限定或大于 cv1 的 cv 限定。如果原始指针值表示内存中一个字节的地址A,并且A不满足T的对齐要求,那么得到的指针值是未指定的。否则,如果原始指针值指向对象 a,并且存在与 a 指针可互转换的类型为 T(忽略 cv 限定)的对象 b,则结果是指向 b 的指针。否则,指针值不会因转换而改变。

结果指针可以未指定或未更改,具体取决于是否int满足 的对齐要求&obj,但代码仍被定义。


推荐阅读