C ++: безопасно ли приводить указатель на int, а затем снова возвращать указатель? - PullRequest
24 голосов
/ 25 августа 2010

Безопасно ли приводить указатель к int, а затем снова к указателю?

Как насчет того, чтобы знать, является ли указатель 32-битным и int 32-битным?

long* juggle(long* p) {
    static_assert(sizeof(long*) == sizeof(int));
    int v = reinterpret_cast<int>(p); // or if sizeof(*)==8 choose long here
    do_some_math(v); // prevent compiler from optimizing
    return reinterpret_cast<long*>(v);
}

int main() {
    long* stuff = new long(42);
    long* ffuts = juggle(stuff); 
    std::cout << "Is this always 42? " << *ffuts << std::endl;
}

Охвачено ли это стандартом?

Ответы [ 12 ]

36 голосов
/ 25 августа 2010

номер

Например, в x86-64 указатель имеет длину 64 бита, но int имеет длину только 32 бита. Преобразование указателя в int и обратно приводит к потере старшего 32-разрядного значения указателя.

Вы можете использовать тип intptr_t в <cstdint>, если вы хотите целочисленный тип, который гарантированно будет таким же, как указатель. Вы можете безопасно переинтерпретировать_каст из указателя на intptr_t и обратно.

22 голосов
/ 26 июля 2013

Да, если ... (или "Да, но ...") и нет в противном случае.

Стандарт определяет (3.7.4.3) следующее:

  • Значение указателя является безопасно полученным указателем [...], если оно является результатом четко определенного преобразования указателя или reinterpret_cast из безопасно полученного значения указателя [или] результатом reinterpret_cast целочисленного представления безопасно полученного значения указателя
  • Целочисленное значение - это целочисленное представление безопасно полученного указателя [...], если его тип по крайней мере равен std::intptr_t и [...] результат reinterpret_cast безопасного производное значение указателя [или] результат действительного преобразования целочисленного представления значения указателя, полученного безопасным способом [или] результат аддитивной или побитовой операции, один из операндов которой является целочисленным представлением безопасное значение указателя
  • Объект отслеживаемого указателя - это [...] объект целочисленного типа, который по крайней мере равен std::intptr_t

Стандарт далее гласит, что реализации могут быть упрощенными или могут быть строгими в отношении принудительного применения производных от указателей безопасных значений. Это означает, что не определено, вызывает ли использование или разыменование небезопасно полученного указателя неопределенное поведение (это забавно!)

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

Следовательно, , если вы либо используете std::intptr_t в первую очередь (предпочтительнее!), Либо если вы знаете, что размер хранилища любого целочисленного типа вы используете (скажем, long) ), по крайней мере, размером std::intptr_t, тогда допустимо и четко определено (то есть «безопасно») привести к вашему целочисленному типу и обратно. Стандарт гарантирует, что.

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

Интересный анекдот состоит в том, что стандарт C ++ не напрямую не определяет std::intptr_t вообще; он просто говорит "то же, что 7.18 в стандарте C" .

Стандарт C, с другой стороны, утверждает, что "обозначает целочисленный тип со знаком со свойством, которое любое допустимое указатель на void может быть преобразован в этот тип, затем преобразован обратно в указатель на void, и результат будет равен исходному указателю ".
Это означает, что без довольно сложных определений, приведенных выше (в частности, последнего бита первой маркированной точки), было бы невозможно преобразовать в / из чего угодно, кроме void*.

21 голосов
/ 25 августа 2010

Да и нет.

В спецификации языка прямо указано, что это безопасно (имеется в виду, что в итоге вы получите исходное значение указателя), пока размер целочисленного типа достаточен для хранения [зависящего от реализации] интегрального представления.указателя.

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

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

5 голосов
/ 25 августа 2010

В общем, нет; указатели могут быть больше int, и в этом случае невозможно восстановить значение.

Если известно, что целочисленный тип достаточно большой, вы можете; в соответствии со Стандартом (5.2.10 / 5):

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

Однако в C ++ 03 нет стандартного способа определить, какие целочисленные типы достаточно велики. C ++ 11 и C99 (и, следовательно, на практике большинство C ++ 03 реализаций), а также Boost.Integer определяют для этого intptr_t и uintptr_t Или вы можете определить свой собственный тип и утверждать (желательно во время компиляции), что он достаточно большой; или, если у вас нет особой причины для целочисленного типа, используйте void*.

3 голосов
/ 25 августа 2010

Это безопасно?Не совсем.

В большинстве случаев это сработает?Да

Конечно, если int слишком мал для хранения полного значения указателя и усечения, вы не получите свой исходный указатель обратно (надеюсь, ваш компилятор предупредит вас об этом случае с помощью усеченных преобразований GCC изуказатели на целые числа являются серьезными ошибками).long или uintptr_t, если ваша библиотека поддерживает это, может быть лучшим выбором.

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

2 голосов
/ 26 июля 2013

Нет, не (всегда) безопасно (следовательно, вообще не безопасно).И это соответствует стандарту .

ISO C ++ 2003, 5.2.10:

  1. Указатель может быть явно преобразован в любой целочисленный тип достаточно большой, чтобы держать его .Функция отображения определяется реализацией.
  2. Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель.Указатель, преобразованный в целое число достаточного размера (, если таковой существует в реализации ) и обратно в тот же тип указателя, будет иметь свое первоначальное значение;в противном случае сопоставления между указателями и целыми числами определяются реализацией.

(Вышеуказанные акценты мои)преобразование является безопасным.

#include <iostream>

// C++03 static_assert.
#define ASSURE(cond) typedef int ASSURE[(cond) ? 1 : -1]

// Assure that the sizes are compatible.
ASSURE(sizeof (int) >= sizeof (char*));

int main() {
    char c = 'A';
    char *p = &c;
    // If this program compiles, it is well formed.
    int i = reinterpret_cast<int>(p);
    p = reinterpret_cast<char*>(i);
    std::cout << *p << std::endl;
}
2 голосов
/ 25 августа 2010

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

MyType* pValue = ...
int stored = (int)pValue; // Just lost the upper 4 bytes on a 64 bit platform
pValue = (MyType*)stored; // pValue is now invalid 
pValue->SomeOp();  // Kaboom
0 голосов
/ 26 июля 2013

Если проблема в том, что вы хотите выполнить обычную математику, вероятно, самым безопасным будет сделать приведение к указателю на символ (или, еще лучше, * uint8_t), выполнить математику, а затем выполнить приведение.это обратно.

0 голосов
/ 30 июня 2013

Инт?Не всегда, если вы работаете на 64-битной машине, тогда int - это всего 4 байта, однако указатели имеют длину 8 байтов, и, следовательно, вы получите другой указатель, когда приведете его обратно к int.

Однако естьспособы обойти это.Вы можете просто использовать 8-байтовый тип данных, который будет работать независимо от того, находитесь ли вы в 32/64-битной системе, например, unsigned long long без знака, потому что вы не хотите расширять знак в 32-битных системах.

Важно отметить, что в Linux unsigned long всегда будет иметь размер указателя *, поэтому, если вы ориентируетесь на системы Linux, вы можете просто использовать это.

* Согласно cppreference , а также тестировал его сам, но не на всех Linux и Linux-подобных системах

0 голосов
/ 25 августа 2010

Если вы собираетесь делать какую-либо переносимость системы, вам нужно использовать что-то вроде Microsoft INT_PTR / UINT_PTR , безопасность после этого зависит от целевых платформ и того, что вы собираетесь делать с INT_PTR.как правило, для большинства арифметических символов char * или uint_8 * лучше работает, будучи безопасным (ish)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...