Как мне обрабатывать преобразование из «void*» в «int» с потерей точности при компиляции 32-битного кода на 64-битной машине?

У меня есть пакет, который компилируется и отлично работает на 32-битной машине. Сейчас я пытаюсь скомпилировать его на 64-битной машине и нахожу следующую ошибку:

 error: cast from ‘void*’ to ‘int’ loses precision

Есть ли флаг компилятора для подавления этих ошибок? или мне нужно вручную редактировать эти файлы, чтобы избежать этих слепков?


person badkya    schedule 08.01.2010    source источник
comment
Есть ли флаг компилятора для подавления этих ошибок? Это не должно быть вашей первой идеей. Предупреждения предназначены для исправления, а не подавления. Отказ от этого предупреждения может иметь фатальное поведение, поскольку ваши адреса будут разделены пополам. Вам нужно исправить использование указателей.   -  person GManNickG    schedule 08.01.2010
comment
Да, но фатальное поведение очень легко заметить...   -  person Inverse    schedule 08.01.2010
comment
Не так просто, как предупреждения компилятора.   -  person Steve Jessop    schedule 08.01.2010
comment
Фатальное поведение может быть незаметным, пока вы уже не использовали 4 ГБ памяти.   -  person bk1e    schedule 08.01.2010
comment
@bk1e: зависит от вашей платформы. В 64-разрядной версии Linux все указатели будут использовать полные 8 байтов, поэтому это будет заметно сразу.   -  person pavpanchekha    schedule 08.01.2010
comment
То же самое в Windows. Это по дизайну.   -  person Hans Passant    schedule 08.01.2010
comment
Что вы подразумеваете под хранением типов объектов?   -  person GManNickG    schedule 10.01.2010
comment
Извините, я имел в виду хранение объектов с разными типами... void* позволяет повторно использовать один и тот же указатель для нескольких типов объектов..   -  person badkya    schedule 10.01.2010
comment
Так что это действительно и адрес, а это значит, что вы не можете просто привести его к другим типам. Вы сделали то, что сказал ответ с наименьшим количеством голосов. Вместо этого используйте intptr_t.   -  person GManNickG    schedule 10.01.2010
comment
Я не согласен с однобокостью всех этих ответов. Я согласен, что если void* является указателем на адресное пространство, потоки или что-то подобное, эти ошибки должны быть исправлены. Однако есть также случаи, когда известно, что преобразование работает, например. если человек сначала преобразует int в void*, а затем хочет преобразовать обратно. Это полностью переносимо (см. документацию по reinterpret_cast). Ответы здесь до сих пор не имеют адекватной поддержки для такой ситуации. int i2 = *((int*)&v); как заявил erco, это начало, но является ли это оптимальным решением?   -  person Cookie    schedule 28.06.2012


Ответы (12)


Проблема в том, что в 32-битном формате int (32-битное целое число) будет содержать значение указателя.

Когда вы переходите на 64-битный, вы больше не можете хранить указатель в int - он недостаточно велик для хранения 64-битного указателя. Для этого предназначен тип intptr_t.

person Reed Copsey    schedule 08.01.2010
comment
(unsigned int) self -› (intptr_t) self у меня работало - person Jacksonkr; 17.11.2015

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

Как вы думаете, произойдет, если вы попытаетесь сохранить 64-битный указатель в 32-битное целое число? Половина ваших данных будет выброшена. Я не могу представить много случаев, когда это правильно или где это не вызовет ошибок.

Исправьте свой код. Или оставайтесь на 32-битной платформе, на которой сейчас работает код.

Если ваш компилятор определяет intptr_t или uintptr_t, используйте их, поскольку они представляют собой целочисленные типы, которые гарантированно будут достаточно большими для хранения указателя.

Если эти типы недоступны, size_t или ptrdiff_t также достаточно велики, чтобы содержать указатель на большинстве (не на всех) платформах. Или используйте long (обычно 64-битный на 64-битных платформах в компиляторе GCC) или long long (тип C99, который большинство, но не все компиляторы, поддерживают в C++), или какой-либо другой целочисленный тип, определяемый реализацией, который, по крайней мере, Ширина 64 бита на 64-битной платформе.

person jalf    schedule 08.01.2010
comment
+1 за ссылку на intptr_t. Это допустимый вариант использования для преобразования объектов из типа void* в целые типы, если у вас есть универсальная функция, которая принимает (void *), потому что пользователь может передавать в нее свои собственные данные, которые могут быть указателем или целым числом. Когда эти пользовательские данные затем передаются обратно в конкретный код, использующий их, он может привести их обратно к целому числу. - person kcstrom; 15.05.2013

Я предполагаю, что ситуация OP заключается в том, что void* используется в качестве общего хранилища для int, где void* больше, чем int. Так, например:

int i = 123;
void *v = (void*)i;    // 64bit void* being (ab)used to store 32bit value
[..]
int i2 = (int)v;       // we want our 32bits of the 64bit void* back

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

Я не собираюсь рассуждать о том, правильно или неправильно так злоупотреблять пустотой*. Если вы действительно хотите обмануть компилятор, следующая техника работает даже с -Wall:

int i2 = *((int*)&v);

Здесь он берет адрес v, преобразует адрес в указатель нужного типа данных, а затем следует за указателем.

person erco    schedule 23.09.2010
comment
Я только что наткнулся на некоторый код, делающий то же самое — да, это не очень хорошо, но мне нужно было решение для его компиляции на 64-битных машинах — спасибо за предупреждение! - person jkp; 16.05.2013

Это ошибка по одной причине: int на вашем компьютере вдвое меньше, чем void*, поэтому вы не можете просто хранить void* в int. Вы потеряете половину указателя, и когда программа позже попытается снова получить указатель из этого int, она не получит ничего полезного.

Даже если бы компилятор не выдал ошибки, код, скорее всего, не заработал бы. Код необходимо изменить и проверить на совместимость с 64-битной системой.

person sth    schedule 08.01.2010
comment
Вы, ребята, все упускаете суть. Он приведение вниз, это обычно используется, когда вам нужно передать либо кучу данных через указатель, либо просто целочисленный аргумент. Что-то еще в вызове, как правило, другой аргумент, сообщает функции, является ли аргумент-указатель на самом деле указателем на некоторые данные или просто целым числом, которое было преобразовано в указатель. Если последнее, то вам нужно «понизить» этот указатель обратно до целого числа. Предупреждения - это просто предупреждения. Это не Божий закон, высеченный в камне, и обращение с ними не помогает. - person Wexxor; 14.07.2012
comment
@Wexxor: Нет, дело в том, что указатель a имеет размер, отличный от int, в его системе. Указатель занимает, вероятно, 8 байтов пространства, int 4 байта. В int недостаточно места для хранения всех данных в указателе, и из-за этого вы потеряете информацию, если преобразуете указатель в int. Вы не можете получить эти четыре байта, которые вы потеряли, обратно, приведя их в другом направлении. Таким образом, в этом случае приведение указателя к int приводит к потере информации, и вы не можете впоследствии вернуться к указателю из int. - person sth; 14.07.2012

Приведение указателя к int ужасно с точки зрения переносимости. Размер int определяется сочетанием компилятора и архитектуры. Вот почему был создан заголовок stdint.h, чтобы вы могли явно указать размер типа, который вы используете на разных платформах с разными размерами слов.

Вам лучше перейти к uintptr_t или intptr_t (из stdint.h и выбрать тот, который лучше всего соответствует нужной вам подписи).

person Matthew Iselin    schedule 08.01.2010
comment
Приведение от указателя к любым данным может быть полезно в пользовательских обратных вызовах. Когда callback отвечает за интерпретацию данных. pthread_create() — это пример. - person ; 12.01.2013

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

person LiraNuna    schedule 08.01.2010

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

Если вы подавите ошибки, это может даже работать какое-то время. Пока указатель указывает на адрес в первых 4 ГБ, старшие 32 бита будут равны 0, и вы не потеряете никаких данных. Но как только вы получите адрес> 4 ГБ, ваш код начнет «таинственным образом» не работать.

Что вам нужно сделать, так это изменить любой int, который может содержать указатель на intptr_t.

person R Samuel Klatchko    schedule 08.01.2010
comment
На самом деле, это, вероятно, будет хорошо работать только в том случае, если оно находится в первом 2G - расширение знака от 2 до 4G даст вам :) - person bdonlan; 24.09.2010

Вы должны вручную отредактировать эти файлы, чтобы заменить их кодом, который вряд ли будет глючным и непереносимым.

person silentbicycle    schedule 08.01.2010

Подавление предупреждений — плохая идея, но может быть флаг компилятора для использования 64-битных целых чисел, в зависимости от вашего компилятора и архитектуры, и это безопасный способ решить проблему (при условии, что что код также не предполагал, что целые числа являются 32-разрядными). Для gcc флаг -m64.

Я полагаю, что лучший ответ по-прежнему состоит в том, чтобы исправить код, но если это устаревший сторонний код и эти предупреждения неистовствуют, я не могу рассматривать этот рефакторинг как очень эффективное использование вашего времени. Тем не менее, определенно не используйте указатели на целые числа в любом из ваших новых кодов.

person user168715    schedule 08.01.2010
comment
Да, это не мой код... В итоге я привел указатель к длинному, и код сработал... вроде... - person badkya; 09.01.2010

Как определено текущим стандартом C++, не существует целочисленного типа, который гарантированно содержит указатель. Некоторые платформы будут иметь intptr_t, но это не стандартная функция C++. По сути, обработка битов указателя, как если бы они были целыми числами, не является переносимой задачей (хотя это можно заставить работать на многих платформах).

Если причина приведения состоит в том, чтобы сделать указатель непрозрачным, тогда void* уже достигает этого, поэтому код может использовать void* вместо int. Typedef может сделать это немного лучше в коде.

typedef void * handle_t;

Если причиной приведения является выполнение арифметики указателя с байтовой гранулярностью, то, вероятно, лучший способ - это привести к (char const *) и выполнить математику с этим.

Если причиной приведения является достижение совместимости с какой-либо существующей библиотекой (возможно, с более старым интерфейсом обратного вызова), которую нельзя изменить, я думаю, вам необходимо просмотреть документацию для этой библиотеки. Если библиотека способна поддерживать требуемые функции (даже на 64-разрядной платформе), ее документация может относиться к предполагаемому решению.

person Brent Bradburn    schedule 09.01.2010

Я столкнулся с подобной проблемой. Я решил это следующим образом:

 #ifdef 64BIT
 typedef uint64_t tulong;
 #else
 typedef uint32_t tulong;
 #endif

 void * ptr = NULL; //Whatever you want to keep it.
 int  i;
 i = (int)(tulong)ptr;

Я думаю, проблема заключается в приведении указателя к более короткому типу данных. Но для более крупного типа до int работает нормально.

Я преобразовал эту проблему из приведения типа указателя к long в приведение типа 64-битного целого числа к 32-битному целому, и это сработало нормально. Я все еще ищу вариант компилятора в GCC/Clang.

person doptimusprime    schedule 04.03.2013
comment
Извините, но это не верное решение. Сначала вы конвертируете в целочисленный тип, достаточно большой, чтобы удерживать указатель, а затем все равно сужаете его. Это означает, что вы будете терять информацию при выполнении приведения в 64-битной системе. Как только вы вернете это целое число обратно к указателю, адрес может оказаться недействительным, и его разыменование вызовет неопределенное поведение. - person jogojapan; 04.03.2013
comment
@jogojapan: Ты прав. Но бывают случаи, когда можно безопасно понизить указатель (например, разница между двумя указателями). Я предоставляю решение, если есть острая необходимость привести указатель к целому числу. Я знаю, что это небезопасно, и нужно учитывать свою логику, прежде чем думать об этом. - person doptimusprime; 05.03.2013
comment
@jogojapan: Есть несколько допустимых случаев, когда это требуется. Рассмотрим случай, когда пользователь должен создать общий массив (в C, где нет шаблона). Тогда в таком случае пользователь может использовать пустые указатели для хранения любого объекта. Однако в то же время, чтобы рассматривать его как массив целых чисел, пользователь также может хранить целые числа в массиве. Вставка будет разрешена, но доступ или поиск могут быть проблематичными из-за ошибки компиляции. Это допустимый случай, когда это требуется. Прежде чем аннулировать что-либо, пожалуйста, всегда сначала учитывайте требование. Также подумайте, зачем это нужно? - person doptimusprime; 12.03.2013
comment
Извините, я проглядел ваш предыдущий ответ на мой комментарий. Чтобы ответить: я говорю не о том, что вы не должны преобразовывать указатели в целые числа. Это прекрасно. Я говорю о том, что вам не следует преобразовывать 64-битные указатели в 32-битные целые числа. ОП попробовал это напрямую, и ваш ответ все еще делает это, за исключением того, что сначала он преобразуется в 64-битное целое число, а затем в 32-битное целое число. Но это не меняет того факта, что это сужение приводит к потере информации. Это будет вызывать неопределенное поведение, как только оно преобразуется обратно в указатель и использует адрес. - person jogojapan; 12.03.2013
comment
@jogjopan: То, что ты говоришь, совершенно верно. Это обязательно приведет к потере информации. Я хотел бы сказать, что могут быть некоторые случаи, когда это необходимо. - person doptimusprime; 12.03.2013
comment
Исходные указатели станут недействительными... для чего это может быть полезно? - person jogojapan; 12.03.2013
comment
Для этого рассмотрим следующий случай: вы сохранили 32-битные целые числа в универсальном массиве (который вы реализуете, используя массив void *). Теперь вы можете легко хранить их в указателях. Позже вы захотите их восстановить. Тогда понижение до 32-бит не должно повлиять на это. В данном случае эти указатели представляют собой 32-битные числа. Теперь вы спросите, зачем такой массив? Это общий массив, в котором хранится любой элемент. Пользователь может использовать один и тот же массив для разных целей. - person doptimusprime; 12.03.2013
comment
Я не понимаю, для чего это хорошо. Если у вас есть 32-битные целые числа (используемые для каких-то общих целей), зачем вам хранить их в указателях? Это не делает их более общими, чем они уже есть. Если вы можете сохранить любой элемент в массиве указателей, то вы можете сохранить любой элемент непосредственно в массиве 32-битных целых чисел. - person jogojapan; 12.03.2013
comment
Если вы хотите игнорировать проблемы до тех пор, пока они не навредят вашим клиентам, вы можете использовать улучшенные сообщения об ошибках GCC 4.7, чтобы понять, что предупреждение выдается -Wpointer-to-int-cast и, следовательно, может быть подавлено -Wno-pointer-to-int-cast. OTOH, я не хочу быть одним из ваших клиентов, если вы делаете это. - person Jonathan Leffler; 18.06.2013
comment
@dbasic: конструкция int i=23; void *p = (void*)i; будет Undefined Behavior, если случайно значение 23 не было возвращено каким-либо более ранним преобразованием указателя в целое число. Есть ли какое-либо основание ожидать, что преобразование указателя в целое число даст что-либо, кроме абстрактного идентификатора указателя (что было бы совершенно бессмысленно, если бы какие-либо биты были потеряны)? - person supercat; 10.02.2014
comment
@supercat: есть только одна основа, которая, я думаю, заключается в том, что когда вы сначала приводите целое число к указателю, а затем получаете целое число по указателю для целочисленного приведения. Если у вас есть массив указателей, но вы храните в нем целое число (хотя и неэффективным способом). - person doptimusprime; 11.02.2014
comment
Конструкция int i=23; void *p = (void*)i;, я не думаю, что это неопределенное поведение, если вы не хотите разыменовывать p. Это целое число для приведения указателя. - person doptimusprime; 11.02.2014
comment
@dbasic: не все представления допускают произвольные комбинации битов в указателе. Компилятору будет разрешено назначать p регистру, который может быть загружен только определенными значениями и не будет точно хранить какие-либо другие. - person supercat; 11.02.2014

Иногда разумно разделить 64-битный элемент на два 32-битных элемента. Вот как вы это сделаете:

Заголовочный файл:

//You only need this if you haven't got a definition of UInt32 from somewhere else
typedef unsigned int UInt32;

//x, when cast, points to the lower 32 bits
#define LO_32(x) (*( (UInt32 *) &x))

//address like an array to get to the higher bits (which are in position 1)
#define HI_32(x) (*( ( (UInt32 *) &x) + 1)) 

Исходный файл:

//Wherever your pointer points to
void *ptr = PTR_LOCATION 

//32-bit UInt containing the upper bits
UInt32 upper_half = HI_32(ptr); 

//32-bit UInt containing the lower bits
UInt32 lower_half = LO_32(ptr); 
person RLLBcheese    schedule 09.07.2014