Преобразование указателя в целое число
Вопрос
Я пытаюсь адаптировать существующий код к 64-битной машине.Основная проблема заключается в том, что в одной функции предыдущий программист использует аргумент void*, который преобразуется в подходящий тип в самой функции.Краткий пример:
void function(MESSAGE_ID id, void* param)
{
if(id == FOO) {
int real_param = (int)param;
// ...
}
}
Конечно, на 64-разрядной машине я получаю сообщение об ошибке:
error: cast from 'void*' to 'int' loses precision
Я хотел бы исправить это, чтобы оно по-прежнему работало на 32-разрядной машине и как можно более чисто.Есть какие - нибудь идеи ?
Решение
Использование intptr_t
и uintptr_t
.
Чтобы убедиться, что он определен переносимым способом, вы можете использовать код, подобный этому:
#if defined(__BORLANDC__)
typedef unsigned char uint8_t;
typedef __int64 int64_t;
typedef unsigned long uintptr_t;
#elif defined(_MSC_VER)
typedef unsigned char uint8_t;
typedef __int64 int64_t;
#else
#include <stdint.h>
#endif
Просто поместите это в какой-нибудь файл .h и включайте везде, где вам это нужно.
Кроме того, вы можете загрузить версию программного обеспечения Microsoft stdint.h
файл из здесь или используйте портативный из здесь.
Другие советы
Я бы сказал, что это современный способ C ++.
#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);
Редактировать:
Правильный тип для целого числа
таким образом, правильный способ сохранить указатель в виде целого числа - использовать uintptr_t
или intptr_t
типы.(Смотрите также в cppreference целочисленные типы для C99).
эти типы определены в <stdint.h>
для C99 и в пространстве имен std
для C ++ 11 в <cstdint>
(см . целочисленные типы для C ++).
Версия C ++ 11 (и более поздних версий)
#include <cstdint>
std::uintptr_t i;
Версия C ++ 03
extern "C" {
#include <stdint.h>
}
uintptr_t i;
Версия C99
#include <stdint.h>
uintptr_t i;
Правильный оператор литья
В C существует только одно приведение, и использование приведения C в C ++ не одобряется (поэтому не используйте его в C ++).В C ++ существуют разные приведения. reinterpret_cast
является ли приведение правильным для этого преобразования (см. Также здесь).
Версия C ++ 11
auto i = reinterpret_cast<std::uintptr_t>(p);
Версия C ++ 03
uintptr_t i = reinterpret_cast<uintptr_t>(p);
Версия C
uintptr_t i = (uintptr_t)p; // C Version
Сопутствующие вопросы
'size_t' и 'ptrdiff_t' необходимы для соответствия вашей архитектуре (какой бы она ни была).Поэтому, я думаю, вместо использования 'int', вы должны иметь возможность использовать 'size_t', который в 64-разрядной системе должен иметь 64-разрядный тип.
Это обсуждение unsigned int vs size_t остановимся немного подробнее.
Использование uintptr_t
как ваш целочисленный тип.
Несколько ответов указывали на uintptr_t
и #include <stdint.h>
как "то самое" решение.Это, я полагаю, часть ответа, но не весь ответ.Вам также нужно посмотреть, где вызывается функция с идентификатором сообщения FOO.
Рассмотрим этот код и компиляцию:
$ cat kk.c
#include <stdio.h>
static void function(int n, void *p)
{
unsigned long z = *(unsigned long *)p;
printf("%d - %lu\n", n, z);
}
int main(void)
{
function(1, 2);
return(0);
}
$ rmk kk
gcc -m64 -g -O -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith \
-Wcast-qual -Wstrict-prototypes -Wmissing-prototypes \
-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE kk.c -o kk
kk.c: In function 'main':
kk.c:10: warning: passing argument 2 of 'func' makes pointer from integer without a cast
$
Вы заметите, что в месте вызова возникла проблема (в main()
) — преобразование целого числа в указатель без приведения.Вам нужно будет проанализировать свой function()
во всех его вариантах использования, чтобы увидеть, как ему передаются значения.Код внутри моего function()
сработало бы, если бы вызовы были записаны:
unsigned long i = 0x2341;
function(1, &i);
Поскольку ваши, вероятно, написаны по-другому, вам нужно просмотреть точки, в которых вызывается функция, чтобы убедиться, что имеет смысл использовать значение, как показано.Не забывайте, что вы, возможно, обнаружили скрытую ошибку.
Кроме того, если вы собираетесь отформатировать значение void *
параметр (в преобразованном виде), внимательно посмотрите на <inttypes.h>
заголовок (вместо stdint.h
— inttypes.h
предоставляет услуги по stdint.h
, что необычно, но стандарт C99 гласит [t] он заголовок <inttypes.h>
включает в себя заголовок <stdint.h>
и расширяет его за счет
дополнительных возможностей, предоставляемых размещенными реализациями) и используйте макросы PRIxxx в ваших строках формата.
Кроме того, мои комментарии строго применимы к C, а не к C ++, но ваш код находится в подмножестве C ++, которое переносимо между C и C ++.Велика вероятность, что мои комментарии применимы.
#include <stdint.h>
- Использование
uintptr_t
стандартный тип, определенный во включенном стандартном заголовочном файле.
Я думаю, что "значение" void * в данном случае является общим дескриптором.Это не указатель на значение, это само значение.(Просто так получилось, что void* используется программистами на C и C ++ именно так.)
Если он содержит целочисленное значение, то лучше бы оно было в пределах целочисленного диапазона!
Вот простой рендеринг в integer:
int x = (char*)p - (char*)0;
Это должно быть только предупреждение.
Я наткнулся на этот вопрос, изучая исходный код SQLite - файл.
В sqliteInt.h, есть абзац кода, определяющий преобразование макроса между целым числом и указателем.Автор сделал очень хорошее заявление, сначала указав, что это должна быть проблема, зависящая от компилятора, а затем реализовал решение, учитывающее большинство популярных компиляторов.
#if defined(__PTRDIFF_TYPE__) /* This case should work for GCC */
# define SQLITE_INT_TO_PTR(X) ((void*)(__PTRDIFF_TYPE__)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(__PTRDIFF_TYPE__)(X))
#elif !defined(__GNUC__) /* Works for compilers other than LLVM */
# define SQLITE_INT_TO_PTR(X) ((void*)&((char*)0)[X])
# define SQLITE_PTR_TO_INT(X) ((int)(((char*)X)-(char*)0))
#elif defined(HAVE_STDINT_H) /* Use this case if we have ANSI headers */
# define SQLITE_INT_TO_PTR(X) ((void*)(intptr_t)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(intptr_t)(X))
#else /* Generates a warning - but it always works */
# define SQLITE_INT_TO_PTR(X) ((void*)(X))
# define SQLITE_PTR_TO_INT(X) ((int)(X))
#endif
И вот цитата из комментария для получения более подробной информации:
/*
** The following macros are used to cast pointers to integers and
** integers to pointers. The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
**
** Ticket #3860: The llvm-gcc-4.2 compiler from Apple chokes on
** the ((void*)&((char*)0)[X]) construct. But MSVC chokes on ((void*)(X)).
** So we have to define the macros in different ways depending on the
** compiler.
*/
Заслуга принадлежит коммиттерам.
Лучшее, что можно сделать, это избегать преобразования из типа указателя в типы, не являющиеся указателями.Однако в вашем случае это явно невозможно.
Как все уже говорили, uintptr_t - это то, что вы должны использовать.
Это Ссылка содержит хорошую информацию о преобразовании в 64-битный код.
Существует также хорошее обсуждение этого вопроса на комп.std.c
С тех пор как uintptr_t
является не гарантируется наличие в C ++ / C ++ 11, если это одностороннее преобразование, вы можете рассмотреть uintmax_t
, всегда определенный в <cstdint>
.
auto real_param = reinterpret_cast<uintmax_t>(param);
Чтобы перестраховаться, можно было бы добавить в любое место кода утверждение:
static_assert(sizeof (uintmax_t) >= sizeof (void *) ,
"No suitable integer type for conversion from pointer type");