请注意,联合类型双关语(写入一个成员然后读取另一个成员)在 ISO C++ 中是未定义的行为。它在 ISO C99 中得到了很好的定义,在 GNU C++ 中作为扩展。 (以及其他一些 C++ 编译器,我认为包括 MSVC。)还要小心作为联合成员的非平凡可复制类型(带有构造函数/析构函数)。
除了类型双关语(例如手动多态性)之外,联合当然还有其他用途,这样的事情可能有意义。
uintptr_t 就是因为这个原因而存在的。(或者 intptr_t 或 ptrdiff_t,如果出于某种原因你想要一个签名类型)。
但对于 float 与 double,您将需要预处理器。 UINTPTR_MAX 提供了一种使用预处理器检查指针宽度的方法,这与 sizeof(void*) 不同
请注意,uintptr_t 通常与指针的宽度相同,但类型名称被定义为可以存储指针值的类型名称。对于 32 位平台上的 floatptr_t,情况并非如此。 (有趣的事实:它将在 x86-64 上用于“规范”48 位地址1)。如果这让您感到困扰或者您担心它会扭曲您对uintptr_t 的看法,请选择其他名称; floatptr_t 看起来很短,即使它是“错误的”。
#include <stdint.h>
// assumption: pointers are 32 or 64 bit, and float/double are IEEE binary32/binary64
#if UINTPTR_MAX > (1ULL<<32)
typedef double floatptr_t;
#else
typedef float floatptr_t;
#endif
static_assert(sizeof(floatptr_t) == sizeof(void*), "pointer width doesn't match float or double, or our UINTPTR_MAX logic is wrong");
union ptrwidth {
uintptr_t u;
intptr_t i;
floatptr_t f;
void *ptr;
};
为了对此进行测试,我使用 x86 32 位 gcc -m32 和 gcc (x86-64) 以及 MSVC 32 和 64 位以及 ARM 32 位编译它 on the Godbolt compiler explorer。
int size = sizeof(ptrwidth);
int size_i = sizeof(ptrwidth::i);
int size_f = sizeof(ptrwidth::f);
int size_ptr = sizeof(ptrwidth::ptr);
# gcc -m32 output
size_ptr: .long 4
size_f: .long 4
size_i: .long 4
size: .long 4
# gcc -m64 output
size_ptr: .long 8
size_f: .long 8
size_i: .long 8
size: .long 8
从而确认联合本身和每个成员都具有预期的大小。
MSVC 也可以,编译成int size_f DD 08H 或04H 等等。
脚注 1:在 x86-64 上,规范虚拟地址是 48 位符号扩展为 64,因此您可以实际上通过 @987654342 往返传递一个指针值@->double 转换和返回没有舍入错误。但不是uintptr_t->double 对于至少不是 2 字节对齐的高半地址。 (并且uint64_t double 转换速度很慢,没有 AVX512F。)
在当前硬件上,非规范虚拟地址错误。
在 32 位模式下,线性地址被限制为 32 位。 PAE 允许多个 32 位进程各自使用不同的 4GB 物理内存,但 seg:off -> 32 位线性发生在页表查找之前。使用 48 位 seg:off 地址不会为您获得更大的地址空间,因此编译器不会这样做。 32 位指针是seg:off 地址的off 部分,段基数固定为零,因此它们与线性虚拟地址相同。与具有 64 位偏移的 64 位模式相同。