Остерегайтесь того, что объединение типов (написание одного члена, а затем чтение другого) является неопределенным поведением в ISO C ++.Это четко определено в ISO C99 и в GNU C ++ как расширение.(И некоторые другие компиляторы C ++, я думаю, в том числе и MSVC.) Также будьте осторожны с нетривиально копируемыми типами (с конструкторами / деструкторами) в качестве членов объединения.
Разумеется, существуют объединения для объединений, отличных от type-наказание (например, раскатанный вручную полиморфизм), где подобное может иметь смысл.
uintptr_t
существует по этой причине. (Или intptr_t
или ptrdiff_t
, если по какой-то причине вам нужен тип со знаком).
Но для float
против double
вам понадобится препроцессор .UINTPTR_MAX
дает вам возможность проверить ширину указателя с препроцессором, в отличие от sizeof(void*)
Обратите внимание, что uintptr_t
обычно такая же ширина, как указатель, но имя типа определяется как имя, которое может сохранить значение указателя.Это не будет иметь место для floatptr_t
на 32-битных платформах.(Интересный факт: он будет на 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;
};
Чтобы проверить это, я скомпилировал его в проводнике компилятора Godbolt с 32-битным x86 gcc -m32
и gcc
(x86-64), а также MSVC 32 и 64-разрядный и ARM 32-разрядный.
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 : на x8-64 канонические виртуальные адреса расширены до 48 битов до 64, поэтому вы можете фактически использовать значение указателя в прямом и обратном направлениях путем преобразования intptr_t
-> double
и обратно без ошибки округления.Но не uintptr_t
-> double
для старших адресов, которые не выровнены как минимум на 2 байта.(И uint64_t
<-> double
медленное преобразование без AVX512F.)
На текущем оборудовании ошибка неканонических виртуальных адресов.