>
>
>
Как корректно привести указатель к int …

Андрей Карпов
Статей: 673

Как корректно привести указатель к int в 64-битной программе?

Наиболее общий ответ - никак.

В 64-битных программах размер указателя равняется 64-битам и его нельзя поместить в тип int, который остался 32-битным практически на всех системах. К исключению относятся экзотические системы с моделью данных SILP64, где размер int также 64-битный. В наиболее распространенных операционных системах (Window, Linux, macOS, ...) используются модели данных LP64 и LLP64, в которых int занимает 32-бита.

Помещение 64-битного указателя в 32-битную переменную ведет к обрезанию старших разрядов и как следствие к некорректной работе программы. Подобный код в программах недопустим:

void *ptr = ...;
int x = (int)ptr;
...
ptr = (void *)x;

Дополнительная опасность подобного кода заключается в том, что он скрывает ошибку, которая может обнаружить себя не сразу. Пока указатели ссылаются на объекты, созданные в младших адресах памяти, программа будет работать корректно, причем возможно долгое время. Однако это только видимость работоспособности программы и сбой может произойти в любой момент (см. пример).

Если по каким-то причинам программисту необходимо хранить указатели в целочисленных типах, то для этого он может использовать memsize-типы, например, intptr_t, size_t, INT_PTR и так далее.

Есть все-таки специфическая ситуация, когда указатель допустимо хранить в 32-битных типах. Речь идет о дескрипторах (handles), которые используются в Windows для работы с различными системными объектами. Примеры таких типов: HANDLE, HWND, HMENU, HPALETTE, HBITMAP и так далее. По сути, эти типы являются указателями. Например, HANDLE объявляется в заголовочных файлах как "typedef void *HANDLE;".

Хотя дескрипторы являются 64-битными указателями, для большей совместимости (например, для возможности взаимодействия между 32-битынми и 64-битными процессами) в них используется только младшие 32-бита. Подробнее смотри "Microsoft Interface Definition Language (MIDL): 64-Bit Porting Guide" (USER and GDI handles are sign extended 32b values).

Такие указатели можно хранить в 32-битным типам данных (например, int, DWORD). Для преобразования таких указателей к 32-битным типам и обратно используются специальные функции:

void            * Handle64ToHandle( const void * POINTER_64 h ) 
void * POINTER_64 HandleToHandle64( const void *h )
long              HandleToLong    ( const void *h )
unsigned long     HandleToUlong   ( const void *h )
void            * IntToPtr        ( const int i )
void            * LongToHandle    ( const long h )
void            * LongToPtr       ( const long l )
void            * Ptr64ToPtr      ( const void * POINTER_64 p )
int               PtrToInt        ( const void *p )
long              PtrToLong       ( const void *p )
void * POINTER_64 PtrToPtr64      ( const void *p )
short             PtrToShort      ( const void *p )
unsigned int      PtrToUint       ( const void *p )
unsigned long     PtrToUlong      ( const void *p )
unsigned short    PtrToUshort     ( const void *p )
void            * UIntToPtr       ( const unsigned int ui )
void            * ULongToPtr      ( const unsigned long ul )

Следует отметить, что простые ошибки приведения указателей к 32-битным типам хорошо диагностируются компилятором Visual C++. Однако во многих старых проектах, содержащих большие сторонние библиотеки, часто бывают отключены многие предупреждения компилятора, что увеличивает вероятность пропуска такого типа ошибок. В описанной ситуации имеет смысл использовать специализированное средство обеспечения переносимости кода на 64-разрядную платформу, например разработанный нами статический анализатор кода Viva64.

Библиографический список