Я пытаюсь адаптировать существующий код к 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 и включите туда, где вам это нужно.
Кроме того, вы можете загрузить версию файла stdint.h от Microsoft из здесь или использовать переносную версию из здесь.
См. stackoverflow.com/questions/126279/… для информации о том, как получить stdint.h, который работает с MSVC (и, возможно, Borland).
Обе ссылки битые!
Этот ответ связан с C, но язык помечен как C++, поэтому это не тот ответ, который я искал.
@HaSeeBMiR Подходящим исправлением является переключение на <cstdint> или загрузка соответствующего cstdint, если вы загружаете stdint.h.
@JustinTime Но он все равно останется C++, добавление <cstdint> или любого файла заголовка c не делает язык c. это расширение компиляторов для поддержки старых файлов заголовков .час в современных компиляторах C++, поэтому C++ должен был поддерживать это и добавлять все файлы заголовков .час C как <c-header-file>. тем не менее, я бы предпочел, чтобы люди отвечали в соответствии с тегами, связанными с тем, что спросил OP.
@HaSeeBMiR Единственная причина, по которой ответ относится к C, а не к C++, заключается в том, что он использует заголовок C вместо эквивалентного заголовка C++. Препроцессор C является частью C++, а cstdint - частью стандарта C++, как и все имена типов, определенные в нем. Это действительно подходит для указанных тегов. ... Я не согласен с определением типов вручную, но это может быть необходимо при работе с компиляторами, которые этого не делают.
Этот ответ невероятно устарел. В наши дни просто #include <cstdint>.
Используйте uintptr_t в качестве целочисленного типа.
Лучше всего избегать преобразования типа указателя в типы, не являющиеся указателями. Однако в вашем случае это явно невозможно.
Как все говорили, uintptr_t - это то, что вам следует использовать.
Этот связь содержит полезную информацию о преобразовании в 64-битный код.
Это также хорошо обсуждается на comp.std.c
Несколько ответов указали на 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++. Шансы на то, что мои комментарии применимы, весьма велики.
Думаю, вы упустили суть моего вопроса. Код сохраняет значение целого числа в указателе. И эта часть кода делает противоположное (например, извлекает значение целого числа, которое было записано как как указатель).
@PierreBdR Тем не менее, он высказывает очень веское мнение. Не всегда так просто смотреть на код (в том числе когда компиляторы предупреждают об этом), который использует подписанный int, но используется для размера, и подумать, что можно изменить его на unsigned. К сожалению, это не всегда так просто. Вы должны подробно рассматривать каждый случай, если только вы не хотите вызвать потенциальные ошибки - и даже тонкие ошибки.
"size_t" и "ptrdiff_t" необходимы для соответствия вашей архитектуре (какой бы она ни была). Поэтому я думаю, что вместо использования int вы должны иметь возможность использовать size_t, который в 64-битной системе должен быть 64-битного типа.
Это обсуждение беззнаковое int против size_t более подробно.
Хотя size_t обычно достаточно велик, чтобы содержать указатель, это не всегда так. Было бы лучше найти заголовок stdint.h (если у вашего компилятора его еще нет) и использовать uintptr_t.
К сожалению, единственным ограничением size_t является то, что он должен содержать результат любого sizeof(). Это не обязательно делает его 64-битным на x64. смотрите также
size_tможет безопасно сохраняет значение указателя, не являющегося членом. См. en.cppreference.com/w/cpp/types/size_t.
@AndyJost Нет, не может. Даже ваша собственная ссылка подтверждает это.
@YoYoYonnY: «На многих платформах (исключение составляют системы с сегментированной адресацией) std :: size_t может безопасно хранить значение любого указателя, не являющегося членом, и в этом случае он является синонимом std :: uintptr_t». - о чем ты говоришь?
#include <stdint.h>uintptr_t, определенный во включенном стандартном файле заголовка.Я думаю, что «значение» void * в данном случае - это универсальный дескриптор. Это не указатель на значение, это само значение. (Именно так void * используется программистами на C и C++.)
Если он содержит целочисленное значение, оно должно быть в целочисленном диапазоне!
Вот простой рендеринг в целое число:
int x = (char*)p - (char*)0;
Он должен только предупреждать.
Я бы сказал, что это современный способ 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
единственный ответ, в котором правильно упоминается reinterpret_cast
Если вы хотели включить <cstdint>, вы, вероятно, также захотите использовать вместо него std :: uintptr_t.
Потрясающе ... Актерский состав - это то, что я искал. Если нам говорят использовать uintptr_t вместо size_t, тогда почему для этого требуется reinterpret_cast? Кажется, что подойдет простой static_cast, поскольку стандарт специально предоставляет совместимые типы данных ...
@jww read: en.cppreference.com/w/cpp/language/static_cast Насколько я понимаю, static_cast может преобразовать тип или, если это указатель, может выполнять корректировку указателя, если типу это нужно. reinterpret_cast на самом деле просто меняет тип базового паттерна памяти (без мутаций). Чтобы уточнить: static_cast здесь ведет себя идентично.
вместо этого он должен быть отмечен как выбранный ответ, поскольку он предоставляет все подробности того, как преобразовать в C и C++.
Итак, есть люди, пишущие на C++, у которых по имени переменной стоит *! Прекрасный. Я съеживаюсь каждый раз, когда вижу код вроде long* j; и даже хуже, когда он на C. Что касается приведений C в C++ (для указателей), я считаю, что более свежие версии GCC жалуются на это или, может быть, даже ограничивают его? А может это был эталон? В противном случае это мое воображение - что, я полагаю, вполне возможно.
Я столкнулся с этим вопросом при изучении исходного кода 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");
Если у вас нет uintptr_t, то uintmax_t тоже не является ответом: нет гарантии, что вы можете сохранить в нем значение указателя! Для этого может не быть целочисленного типа.
Я знаю, что это копание старого сообщения, но похоже, что принятый ответ не совсем правильный. Конкретным примером неработающего
size_tявляется сегментированная память i386. Хотяsizeofявляется 32-битным компьютером, он возвращает2дляsize_t. Алекс ответ ниже кажется правильным. Ответ Алекса иuintptr_tработает практически везде и теперь является стандартом. Он обеспечивает обработку C++ 11 и даже дает защиту заголовка C++ 03.