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

88

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

PierreBdR
источник
5
Я знаю, что это копание старого сообщения, но похоже, что принятый ответ не совсем правильный. Конкретный пример size_tнеработающей - сегментированная память i386. Хотя 32-битной машине, sizeofвозвращается 2для size_t. Ответ Алекса ниже кажется правильным. Ответ Алекс и uintptr_tработает почти везде, и теперь это стандарт. Он обеспечивает обработку C ++ 11 и даже дает защиту заголовка C ++ 03.
jww

Ответы:

70

Используйте 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файла от Microsoft отсюда или использовать переносную отсюда .

Милан Бабушков
источник
См. Stackoverflow.com/questions/126279/… для получения информации о том, как получить stdint.h, который работает с MSVC (и, возможно, с Borland).
Майкл Берр,
2
Обе ссылки битые!
Антонио
1
Этот ответ связан с C, но язык помечен как C ++, поэтому это не тот ответ, который я искал.
HaseeB Mir
@HaSeeBMiR Подходящим исправлением является переключение на <cstdint>или загрузка соответствующего исправления, cstdintесли вы загружаете файл stdint.h.
Джастин Тайм - Восстановить Монику
1
@HaSeeBMiR Единственная причина, по которой ответ относится к C, а не к C ++, заключается в том, что он использует заголовок C вместо эквивалентного заголовка C ++. Препроцессор C является частью C ++ и cstdintчастью стандарта C ++, как и все имена типов, определенные в нем. Это действительно подходит для указанных тегов. ... Я не согласен с определением типов вручную, но это может быть необходимо при работе с компиляторами, которые этого не делают.
Джастин Тайм - Восстановить Монику
94

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

Связанные вопросы

Александр О
источник
6
единственный ответ, в котором правильно упоминается reinterpret_cast
Plasmacel
Если вы хотели включить <cstdint>, вы, вероятно, также захотите использовать вместо него std :: uintptr_t.
linleno
Круто ... Актерский состав - это то, что я искал. Если нам говорят использовать uintptr_tвместо size_t, то зачем это нужно reinterpret_cast? Кажется, что это просто static_cast, поскольку стандарт специально предоставляет совместимые типы данных ...
jww
1
@jww read: en.cppreference.com/w/cpp/language/static_cast, как я понимаю, это static_castможет преобразовать тип или, если это указатель, может выполнять корректировку указателя, если тип нуждается в этом. reinterpret_castна самом деле просто меняет тип базового паттерна памяти (без мутаций). чтобы уточнить: здесь static_castведет себя идентично.
Александр О
2
вместо этого он должен быть отмечен как выбранный ответ, поскольку он предоставляет все подробности о том, как преобразовать в C и C ++ .
HaseeB Mir
43

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

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

Ричард Корден
источник
34
Хотя size_t обычно достаточно велик, чтобы содержать указатель, это не всегда так. Было бы лучше найти заголовок stdint.h (если у вашего компилятора его еще нет) и использовать uintptr_t.
Майкл Берр,
3
К сожалению, единственным ограничением size_tявляется то, что он должен содержать любой результат sizeof(). Это не обязательно делает его 64-битным на x64. см. также
Antoine
3
size_t может безопасно хранить значение указателя, не являющегося членом. См. En.cppreference.com/w/cpp/types/size_t .
AndyJost
2
@AndyJost Нет, не может. Даже ваша собственная ссылка подтверждает это.
yyny
1
@YoYoYonnY: «На многих платформах (исключение составляют системы с сегментированной адресацией) std :: size_t может безопасно хранить значение любого указателя, не являющегося членом, и в этом случае он является синонимом std :: uintptr_t». - о чем ты говоришь?
slashmais
16

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

Лунная тень
источник
8

В нескольких ответах указывалось 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
$

Вы заметите, что в месте вызова (in 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 ++. Шансы на то, что мои комментарии применимы, весьма велики.

Джонатан Леффлер
источник
3
Я думаю, вы упустили суть моего вопроса. Код хранит значение целого числа в указателе. И эта часть кода делает противоположное (например, извлекает значение целого числа, которое было записано как указатель).
PierreBdR
@PierreBdR Тем не менее, он высказывает очень веское мнение. Не всегда так просто смотреть на код (в том числе когда компиляторы предупреждают об этом), который использует подписанный int, но используется для размера, и подумать, что можно изменить его на unsigned. К сожалению, это не всегда так просто. Вы должны подробно рассматривать каждый случай, если только вы не хотите вызвать потенциальные ошибки - и даже тонкие ошибки.
Pryftan
4
  1. #include <stdint.h>
  2. Используйте uintptr_tстандартный тип, определенный во включенном стандартном файле заголовка.
Майкл С.
источник
4

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

Кредит принадлежит коммиттерам.

B.Mr.W.
источник
2

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

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

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

Это также хорошо обсуждается на comp.std.c

Бенуа
источник
2

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

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

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

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

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

Крейг Хикс
источник
0

Так 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");
Антонио
источник
Если у вас нет uintptr_t, то uintmax_t тоже не является ответом: нет гарантии, что вы можете сохранить в нем значение указателя! Для этого может не быть целочисленного типа.
PierreBdR