Замечания: Этот вопрос был переименован и сокращен, чтобы сделать его более целенаправленным и читабельным. Большинство комментариев относятся к старому тексту.
Согласно стандарту, объекты другого типа могут не использовать одну и ту же область памяти. Так что это не будет законно:
std::array<short, 4> shorts;
int* i = reinterpret_cast<int*>(shorts.data()); // Not OK
Стандарт, однако, допускает исключение из этого правила: любой объект может быть доступен через указатель на char
или же unsigned char
:
int i = 0;
char * c = reinterpret_cast<char*>(&i); // OK
Однако мне не ясно, разрешено ли это и наоборот. Например:
char * c = read_socket(...);
unsigned * u = reinterpret_cast<unsigned*>(c); // huh?
Часть вашего кода сомнительна из-за задействованных преобразований указателей. Имейте в виду, что в этих случаях reinterpret_cast<T*>(e)
имеет семантику static_cast<T*>(static_cast<void*>(e))
потому что типы, которые вовлечены, являются стандартным расположением. (Я бы порекомендовал вам всегда использование static_cast
с помощью cv void*
когда дело касается хранения.)
Внимательное прочтение стандарта предполагает, что во время преобразования указателя в или из T*
предполагается, что действительно существует реальный объект T*
вовлеченный — который трудно выполнить в некоторых ваших фрагментах, даже когда он «обманывает» благодаря тривиальности задействованных типов (подробнее об этом позже). Это было бы, кроме того, потому что …
Псевдоним не о преобразованиях указателей. Это текст C ++ 11, в котором изложены правила, которые обычно называются правилами «строгого алиасинга», из 3.10 Lvalues и rvalues [basic.lval]:
10 Если программа пытается получить доступ к сохраненному значению объекта через glvalue другого, чем один из следующих типов, поведение не определено:
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, подобный (как определено в 4.4) динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- агрегатный или объединенный тип, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных субагрегата или содержащего объединения),
- тип, который является (возможно, квалифицированным cv) типом базового класса динамического типа объекта,
- тип char или unsigned char.
(Это абзац 15 того же пункта и подпункта в C ++ 03, с некоторыми незначительными изменениями в тексте с использованием, например, «lvalue» вместо «glvalue», поскольку последний является понятием C ++ 11.)
В свете этих правил давайте предположим, что реализация предоставляет нам magic_cast<T*>(p)
который «каким-то образом» преобразует указатель в другой тип указателя. Обычно это было бы быть reinterpret_cast
, который в некоторых случаях дает неуказанные результаты, но, как я объяснил ранее, это не так для указателей на типы стандартной компоновки. Тогда совершенно верно, что все ваши фрагменты верны (заменяя reinterpret_cast
с magic_cast
), потому что никакие глюки не связаны вообще с результатами magic_cast
,
Вот фрагмент, который появляется неправильно использовать magic_cast
, но я буду утверждать, это правильно:
// assume constexpr max
constexpr auto alignment = max(alignof(int), alignof(short));
alignas(alignment) char c[sizeof(int)];
// I'm assuming here that the OP really meant to use &c and not c
// this is, however, inconsequential
auto p = magic_cast<int*>(&c);
*p = 42;
*magic_cast<short*>(p) = 42;
Чтобы оправдать мои рассуждения, предположим, что это поверхностно другой фрагмент:
// alignment same as before
alignas(alignment) char c[sizeof(int)];
auto p = magic_cast<int*>(&c);
// end lifetime of c
c.~decltype(c)();
// reuse storage to construct new int object
new (&c) int;
*p = 42;
auto q = magic_cast<short*>(p);
// end lifetime of int object
p->~decltype(0)();
// reuse storage again
new (p) short;
*q = 42;
Этот фрагмент тщательно построен. В частности, в new (&c) int;
Мне разрешено использовать &c
даже если c
был уничтожен по правилам, изложенным в пункте 5 из 3.8 Срок службы объекта [basic.life]. В параграфе 6 того же самого правила очень похожи на ссылки на хранилище, а в параграфе 7 объясняется, что происходит с переменными, указателями и ссылками, которые использовались для ссылки на объект после повторного использования его хранилища — я буду называть их как 3.8 / 5- 7.
В этом случае &c
(неявно) преобразуется в void*
, что является одним из правильного использования указателя на хранилище, которое еще не использовалось. так же p
получается из &c
перед новым int
построен Его определение может быть перенесено после уничтожения c
в зависимости от того, насколько глубока магия реализации, но, конечно, не после int
Конструкция: пункт 7 будет применяться, и это не одна из разрешенных ситуаций. Строительство short
объект также опирается на p
становится указателем на хранилище.
Теперь, потому что int
а также short
тривиальные типы, мне не нужно использовать явные вызовы деструкторов. Мне также не нужны явные вызовы конструкторам (то есть вызовы обычного стандартного размещения, объявленного в <new>
). С 3.8 Срок жизни объекта [basic.life]:
1 […] Время жизни объекта типа T начинается, когда:
- хранение с правильным выравниванием и размером для типа T, и
- если объект имеет нетривиальную инициализацию, его инициализация завершена.
Время жизни объекта типа T заканчивается, когда:
- если T является типом класса с нетривиальным деструктором (12.4), начинается вызов деструктора, или
- хранилище, которое занимает объект, используется повторно или освобождается.
Это означает, что я могу переписать код так, чтобы после сворачивания промежуточной переменной q
Я заканчиваю с оригинальным фрагментом.
Обратите внимание, что p
нельзя сложить То есть следующее определенно неверно:
alignas(alignment) char c[sizeof(int)];
*magic_cast<int*>(&c) = 42;
*magic_cast<short*>(&c) = 42;
Если мы предположим, что int
объект (тривиально) строится со второй строкой, тогда это должно означать &c
становится указателем на хранилище, которое было повторно использовано. Таким образом, третья строка неверна — хотя из-за 3.8 / 5-7, а не из-за правил псевдонимов, строго говоря.
Если мы этого не предполагаем, то вторая строка является нарушение правил псевдонимов: мы читаем, что на самом деле char c[sizeof(int)]
объект через glvalue типа int
, что не является одним из разрешенных исключений. По сравнению, *magic_cast<unsigned char>(&c) = 42;
было бы хорошо (мы предполагаем, short
объект тривиально построен на третьей строке).
Как и Alf, я бы также порекомендовал вам использовать стандартное размещение как новое при использовании хранилища. Пропуск уничтожения для тривиальных типов это хорошо, но при встрече *some_magic_pointer = foo;
вы, скорее всего, столкнетесь либо с нарушением 3.8 / 5-7 (независимо от того, каким волшебным образом был получен этот указатель), либо с правилами наложения имен. Это также означает сохранение результата нового выражения, так как вы, скорее всего, не сможете повторно использовать магический указатель после того, как ваш объект создан — снова из-за 3.8 / 5-7.
Чтение байтов объекта (это означает использование char
или же unsigned char
) хорошо, но вы даже не используете reinterpret_cast
или что-нибудь волшебное вообще. static_cast
с помощью cv void*
возможно, подходит для этой работы (хотя я чувствую, что Стандарт мог бы использовать там более качественную формулировку).
Это тоже:
// valid: char -> type
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);
Это не правильно. Правила псевдонимов устанавливают, при каких обстоятельствах доступ к объекту через lvalue другого типа является законным / незаконным. Существует определенное правило, которое говорит, что вы можете получить доступ к любому объекту через указатель типа char
или же unsigned char
Итак, первый случай верен. То есть A => B не обязательно означает B => A. Вы можете получить доступ к int
через указатель на char
, но вы не можете получить доступ к char
через указатель на int
,
В интересах Альфа:
Если программа пытается получить доступ к сохраненному значению объекта через glvalue другого, чем один из следующих типов, поведение не определено:
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, подобный (как определено в 4.4) динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных субагрегата или содержащего объединения),
- тип, который является (возможно, квалифицированным cv) типом базового класса динамического типа объекта,
- тип char или unsigned char.
Что касается действительности …
alignas(int) char c[sizeof(int)];
int * i = reinterpret_cast<int*>(c);
reinterpret_cast
само по себе нормально или нет, в смысле создания полезного значения указателя, в зависимости от компилятора. И в этом примере результат не используется, в частности, массив символов не доступен. Так что о примере как можно больше сказать нечего: просто зависит.
Но давайте рассмотрим расширенную версию, которая затрагивает правила псевдонимов:
void foo( char* );
alignas(int) char c[sizeof( int )];
foo( c );
int* p = reinterpret_cast<int*>( c );
cout << *p << endl;
И давайте рассмотрим только случай, когда компилятор гарантирует полезное значение указателя, которое поместило бы pointee в те же байты памяти (причина, по которой это зависит от компилятора, заключается в том, что стандарт в §5.2.10 / 7 только гарантирует это для преобразования указателей, где типы совместимы с выравниванием и в противном случае оставляют его как «неопределенный» (но тогда весь §5.2.10 несколько несовместим с §9.2 / 18).
Теперь, одна интерпретация §3.10 / 10 стандарта, так называемое предложение «строгого алиасинга» (но обратите внимание, что в стандарте никогда не используется термин «строгое алиасинг»),
Если программа пытается получить доступ к сохраненному значению объекта через glvalue другого, чем один из следующих типов, поведение не определено:
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, подобный (как определено в 4.4) динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных субагрегата или содержащего объединения),
- тип, который является (возможно, квалифицированным cv) типом базового класса динамического типа объекта,
char
или жеunsigned
char
тип.
является то, что, как оно само говорит, касается динамический тип объекта, проживающего в c
байт.
При такой интерпретации операция чтения на *p
в порядке, если foo
разместил int
объект есть, а иначе нет. Так что в этом случае char
доступ к массиву через int*
указатель. И никто не сомневается, что другой способ действительно: хотя foo
возможно, поместил int
объект в этих байтах, вы Можно свободный доступ к этому объекту в виде последовательности char
значения, по последнему пункту §3.10 / 10.
Так что с этой (обычной) интерпретацией, после foo
разместил int
там мы можем получить к нему доступ как char
объекты, поэтому хотя бы один char
объект существует в области памяти с именем c
; и мы можем получить к нему доступ как int
так что хотя бы тот int
существует и там; так что Утверждение Давида в другом ответе тот char
объекты не могут быть доступны как int
несовместим с этой обычной интерпретацией.
Утверждение Дэвида также несовместимо с наиболее распространенным использованием размещения новых.
Что касается других возможных интерпретаций, которые, возможно, могли бы быть совместимы с утверждением Дэвида, ну, я не могу придумать ни одного, которое имеет смысл.
Итак, в заключение, что касается Священного Стандарта, просто T*
указатель на массив практически полезен или не зависит от компилятора, а доступ к указанному значению может быть действительным или нет в зависимости от того, что присутствует. В частности, подумайте о представлении ловушек int
: ты бы не хотел, чтобы это взрывалось на тебе, если бы это был битпаттерн. Поэтому, чтобы быть в безопасности, вы должны знать, что там, биты и как призыв к foo
выше иллюстрирует может компилятор вообще не знает что, Например, строгий оптимизатор, основанный на выравнивании, может не знать, что…