Создать объединение размером с указатель для 64- и 32-битных сборок - PullRequest
0 голосов
/ 14 октября 2018

Я хочу создать объединение, подобное следующему

union {
    long i;
    float f;
    void* ptr;
};

, в котором члены i и f всегда будут иметь размер ptr (float & long для 32-битных / double и long long для 64-битных).

Каков наилучший способ достичь этого с минимальным использованием макроса?

1 Ответ

0 голосов
/ 14 октября 2018

Остерегайтесь того, что объединение типов (написание одного члена, а затем чтение другого) является неопределенным поведением в 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.)

На текущем оборудовании ошибка неканонических виртуальных адресов.

...