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


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

10 72

10 ответов:

использовать 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 С здесь или использовать портативный от здесь.

Я бы сказал, что это современный способ C++.

#include <cstdint>
void *p;
auto i = reinterpret_cast<std::uintptr_t>(p);

EDIT:

правильный тип для целого числа

поэтому правильный способ хранения указателя в виде целого числа-использовать uintptr_t или intptr_t типы. (См. также в cppreference целочисленные типы для C99).

эти типы определены в <stdint.h> для C99 и в пространстве имен std для C++11 in <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 cast в 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 как ваш тип integer.

несколько ответов указали на 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++. Шансы являются справедливыми для хорошо, что мои комментарии применимы.

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

если он содержит целочисленное значение, оно должно быть в целочисленном диапазоне!

вот простой рендеринг на целое число:

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

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

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

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

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

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

есть также хорошее обсуждение этого на comp.станд.c

я наткнулся на этот вопрос при изучении исходного кода 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 и не гарантируется быть там в 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");