С++: безопасно ли указывать указатель на 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;
}
Это покрывается стандартом?
Ответы
Ответ 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
), выполнить свою математику и затем отбросьте его.