Наиболее общий ответ - никак.
В 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.