Подтвердить что ты не робот

С++: безопасно ли указывать указатель на int и позже на указатель?

Безопасно ли указывать указатель на 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;
}

Это покрывается стандартом?

4b9b3361

Ответ 1

Нет.

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

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

Ответ 2

Да и нет.

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

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

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

Ответ 3

Да, если... (или "Да, но..." ) и нет.

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

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

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

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

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

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

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

Ответ 6

Абсолютно нет. Выполнение некоторых ошибочно полагает, что размер 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

Ответ 7

Нет, это не (всегда) безопасно (таким образом, не безопасно вообще). И это покрывается стандартом.

ISO С++ 2003, 5.2.10:

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

(Вышеупомянутые акценты принадлежат мне.)

Поэтому, если вы знаете, что размеры совместимы, конвертирование безопасно.

#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;
}

Ответ 8

Используйте uintptr_t из "stdint.h" или из "boost/stdint.h". Гарантируется наличие достаточного количества памяти для указателя.

Ответ 9

Нет, это не так. Даже если мы исключаем проблему архитектуры, размер указателя и целого числа имеет отличия. Указатель может быть трех типов в С++: рядом, далеко и огромен. Они имеют разные размеры. И если мы говорим о целочисленном, то обычно это 16 или 32 бит. Таким образом, отличное целое число в указатели и наоборот не является безопасным. Необходимо проявлять особую осторожность, так как есть очень большие шансы на потерю точности. В большинстве случаев целое число будет меньше места для хранения указателя, что приведет к потере стоимости.

Ответ 10

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

Ответ 11

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

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

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

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

Ответ 12

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