Преобразование указателя в целое число

Я пытаюсь адаптировать существующий код к 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-битной машине и был максимально чистым. Любая идея?

Ответ 1

Используйте 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 и включите туда, где вам это нужно.

В качестве альтернативы вы можете загрузить версию stdint.h Microsofts из здесь или использовать переносимый из здесь.

Ответ 2

Я бы сказал, что это современный 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

Смежные вопросы

Ответ 3

'size_t' и 'ptrdiff_t' должны соответствовать вашей архитектуре (независимо от того, что это такое). Поэтому я считаю, что вместо использования "int" вы должны использовать "size_t", который в 64-битной системе должен быть 64-разрядным типом.

Это обсуждение unsigned int vs size_t немного подробнее.

Ответ 4

Используйте uintptr_t как целочисленный тип.

Ответ 5

Несколько ответов указывают на 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 говорит, что заголовок <inttypes.h> включает заголовок <stdint.h> и расширяет его с помощью дополнительные возможности, предоставляемые размещенными реализациями) и использовать макросы PRIxxx в строках формата.

Кроме того, мои комментарии строго применимы к C, а не к С++, но ваш код находится в подмножестве С++, который переносится между C и С++. Шансы справедливы, что мои комментарии применимы.

Ответ 6

  • #include <stdint.h>
  • Используйте стандартный uintptr_t стандартный тип, указанный в включенном стандартном файле заголовка.

Ответ 7

Я думаю, что "значение" void * в этом случае является общим дескриптором. Это не указатель на значение, это само значение. (Это как раз то, как void * используется программистами C и С++.)

Если он удерживает целочисленное значение, он лучше всего находиться в целочисленном диапазоне!

Здесь легко преобразование в целое число:

int x = (char*)p - (char*)0;

Он должен давать только предупреждение.

Ответ 8

Я столкнулся с этим вопросом, изучая исходный код 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.
*/

Кредит отправляется коммиттерам.

Ответ 9

Лучше всего сделать это, чтобы избежать преобразования из типа указателя в типы без указателей. Однако это явно невозможно в вашем случае.

Как говорили все, uintptr_t - это то, что вы должны использовать.

Эта ссылка имеет хорошую информацию о преобразовании в 64-разрядный код.

Существует также хорошее обсуждение этого вопроса на comp.std.c

Ответ 10

Поскольку 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");