Псевдоним T* с char* разрешен. Допускается ли и наоборот?

Примечание. Этот вопрос был переименован и сокращен, чтобы сделать его более точным и понятным. Большинство комментариев относятся к старому тексту.


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

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?

person StackedCrooked    schedule 27.09.2012    source источник
comment
Я не верю, что второй действителен. Разыменование i нарушит строгий псевдоним.   -  person Mysticial    schedule 27.09.2012
comment
Вот тут и появляется memcpy...   -  person ildjarn    schedule 27.09.2012
comment
@Mysticial не new int делает что-то вроде static_cast<int*>(malloc(sizeof(int)) под капотом?   -  person StackedCrooked    schedule 27.09.2012
comment
При чем тут new int? РЕДАКТИРОВАТЬ: О, я считаю, что malloc() является одним из исключений. Я не совсем уверен, что говорит стандарт. Но возвращаясь ко второму случаю, предположим, что c смещено, а процессор не поддерживает смещенный доступ.   -  person Mysticial    schedule 27.09.2012
comment
Ах, значит, c может быть смещено. Я еще не рассматривал это.   -  person StackedCrooked    schedule 27.09.2012
comment
Вам разрешено приводить malloc() к любому из встроенных типов, поскольку он гарантированно будет выровнен со всеми встроенными типами. Но если вы поместите char[] в стек, не гарантируется, что он будет выровнен по int. Я не знаю, как стандартные слова, хотя.   -  person Mysticial    schedule 27.09.2012
comment
Только первый действителен: вы можете интерпретировать все как массив символов. Однако вы не можете не интерпретировать массив символов как что-либо еще.   -  person Kerrek SB    schedule 27.09.2012
comment
@KerrekSB Итак, я не могу привести результат malloc к определенному типу? Или это в основном вопрос мировоззрения, как, кажется, предполагает Mysticial.   -  person StackedCrooked    schedule 27.09.2012
comment
@StackedCrooked Вы наверняка можете. Но я не уверен, как стандартные слова допускают это исключение.   -  person Mysticial    schedule 27.09.2012
comment
Результат malloc гарантированно будет максимально выровнен, поэтому вы можете использовать его для любого встроенного типа. Типы с избыточным выравниванием нуждаются в собственной обработке памяти. (Но malloc не имеет ничего общего с каламбуром или алиасингом типов.)   -  person Kerrek SB    schedule 27.09.2012
comment
Я считаю, что new char[] также гарантированно будет максимально выровнен. В противном случае сложно писать контейнеры шаблонов. РЕДАКТИРОВАТЬ: stackoverflow.com/questions/506518/   -  person Mysticial    schedule 27.09.2012
comment
Что касается этого, тот же вопрос, а именно выравнивание. должен получить это выравнивание правильно.   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
@Cheersandhth.-Альф, что в этом коде нарушает правила псевдонимов (в дополнение к нарушению правил выравнивания)?   -  person StackedCrooked    schedule 27.09.2012
comment
@StackerCrooked: кажется, все в порядке (если не учитывать выравнивание). любой POD может быть доступен как последовательность char, и наоборот. но я бы использовал новое место размещения вместо reinterpret_cast...   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
@Cheersandhth.-Alf А ты уверен, что наоборот? Я постоянно слышу, что вы не можете использовать псевдоним массива char с другим типом.   -  person Mysticial    schedule 27.09.2012
comment
подождите, мне нужно скопировать стандартные документы с USB-накопителя   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
3.9.2 Для любого объекта (кроме подобъекта базового класса) тривиально копируемого типа T, независимо от того, содержит ли объект действительное значение типа T, базовые байты (1.7), составляющие объект, могут быть скопированы в массив char или unsigned char. Если содержимое массива char или unsigned char копируется обратно в объект, объект впоследствии сохраняет свое исходное значение.   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
alignas(int) char c[sizeof(int)]; должно быть достаточно, чтобы получить надлежащее хранилище для int. std::aligned_storage<sizeof(int), alignof(int)>::type c; является альтернативой.   -  person Luc Danton    schedule 27.09.2012
comment
также 3.10.10 последний тире тип char или unsigned char.   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
@Cheersandhth.-Alf Я полагаю, что placement new предпочтительнее, потому что он также вызывает конструктор (если он есть). Тем не менее, я предполагаю, что это не помогает с выравниванием?   -  person StackedCrooked    schedule 27.09.2012
comment
Правила стандарта не распространяются на код реализации; new может делать все, что захочет, чтобы достичь своей спецификации.   -  person Jim Balter    schedule 27.09.2012
comment
@StackedCrooked: да, новое размещение более чистое и надежное (вызов конструктора). и AFAIK это не помогает с выравниванием.   -  person Cheers and hth. - Alf    schedule 27.09.2012
comment
@ildjarn Хороший вопрос. Поскольку memcpy имеет «чрезвычайно определенное» поведение и, как я читал, может быть оптимизирован в тот же код, что и рассматриваемый здесь случай, я часто просто использую это, вместо того, чтобы ломать себе голову, пытаясь извлечь какой-либо реальный ответ из ужасной комбинации тайные стандартные и бесполезные личные аргументы в таких темах.   -  person underscore_d    schedule 05.06.2016
comment
@underscore_d: стандарт C 99 явно указывает, что memcpy можно использовать для чтения хранилища, записанного как данные неизвестного типа, в хранилище с объявленным типом, но, как правило, нельзя использовать для записи данных, которые были записаны с одним типом таким образом как быть читаемым с помощью другого. Стандарт C++ не указывает явно, как работает memcpy, за исключением того, что он работает так же, как в C.   -  person supercat    schedule 03.09.2016


Ответы (3)


Часть вашего кода вызывает сомнения из-за задействованных преобразований указателя. Имейте в виду, что в этих случаях 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 Если программа пытается получить доступ к хранимому значению объекта через значение gl, отличное от одного из следующих типов, поведение не определено:

  • динамический тип объекта,
  • cv-квалифицированная версия динамического типа объекта,
  • тип, аналогичный (как определено в 4.4) динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим cv-квалифицированной версии динамического типа объекта,
  • агрегатный тип или тип объединения, который включает один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных подагрегата или содержащегося объединения),
  • тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
  • тип char или unsigned char.

(Это параграф 15 того же пункта и подпункта в С++ 03, с некоторыми незначительными изменениями в тексте, например, «lvalue» используется вместо «glvalue», поскольку последнее является понятием C++ 11.)

В свете этих правил давайте предположим, что реализация предоставляет нам magic_cast<T*>(p), который «каким-то образом» преобразует указатель в другой тип указателя. Обычно это было бы равно reinterpret_cast, что в некоторых случаях приводит к неопределенным результатам, но, как я объяснял ранее, это не так для указателей на типы стандартного макета. Тогда совершенно очевидно, что все ваши фрагменты правильны (замена reinterpret_cast на magic_cast), потому что никакие значения gl не связаны с результатами 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 тривиально создается в третьей строке).

Как и Альф, я также рекомендую вам явно использовать новое стандартное размещение при использовании хранилища. Пропускать уничтожение тривиальных типов — это нормально, но встречая *some_magic_pointer = foo;, вы, скорее всего, сталкиваетесь либо с нарушением 3.8/5-7 (независимо от того, каким волшебным образом был получен этот указатель), либо с нарушением правил псевдонимов. Это также означает сохранение результата нового выражения, поскольку вы, скорее всего, не сможете повторно использовать магический указатель после создания вашего объекта - снова из-за 3.8/5-7.

Однако чтение байтов объекта (это означает использование char или unsigned char) прекрасно, и вам даже не нужно использовать reinterpret_cast или вообще что-то магическое. static_cast через cv void*, возможно, подходит для этой работы (хотя мне кажется, что в Стандарте можно было бы использовать более удачную формулировку).

person Luc Danton    schedule 27.09.2012
comment
Спасибо за исчерпывающий обзор. - person StackedCrooked; 29.09.2012
comment
Почему можно использовать void* в качестве псевдонима? (Его нет в списке разрешенных типов.) - person Alexandre Hamez; 03.09.2014
comment
@AlexandreHamez Обратите внимание, что void неполное. Таким образом, вы можете иметь void*, который содержит тот же адрес, что и другой указатель (одно из значений псевдонимов), но это нормально, потому что вы все равно не можете читать из него — а так называемые правила псевдонимов C++ связаны с чтением. И на самом деле передача адресных значений в любом виде сама по себе не может противоречить правилам псевдонимов (но существует множество других правил…). Это имеет для вас смысл? (Я бы улучшил этот ответ, если вы чувствуете, что некоторые части недостаточно ясны.) - person Luc Danton; 03.09.2014
comment
@LucDanton Спасибо, теперь все ясно. Я не понимал, что чтение из указателя было ключом. Если я правильно понимаю, это означает, что пока я не читаю из указателя, который может иметь неправильный тип (в отношении правил алиасинга), то все в порядке. Я думал, что создания указателя с неправильным типом было достаточно, чтобы сломать псевдоним. - person Alexandre Hamez; 03.09.2014
comment
@AlexandreHamez Вот именно. - person Luc Danton; 03.09.2014
comment
@LucDanton Спасибо! - person Alexandre Hamez; 03.09.2014
comment
В вашем заведомо неверном представлении ваш аргумент основан на чтении того, что на самом деле является объектом char c[sizeof(int)], через glvalue типа int -- за исключением того, что вы его вообще не читаете, вы сохранение значения, которое определенно имеет тип int в этом месте. - person Ben Voigt; 11.01.2017
comment
@BenVoigt Я понимаю вашу точку зрения, хотя я все еще думаю, что пример верен по духу. Если предполагается, что объект int не находится в этом месте, назначение является фиктивным. Я не уверен, как правильно это выразить, «чтение» (мои слова) / «доступ к сохраненному значению» (стандартный вариант) может не совсем точно описывать, что происходит во время присваивания. - person Luc Danton; 11.01.2017
comment
Мужчина. Этот ответ полностью нуждается в TL; DR wrt. к фактическому названию вопроса! Все еще проголосовали. Хорошее рассуждение. - person Martin Ba; 13.01.2017

Это тоже:

// 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.


В интересах Альфа:

Если программа пытается получить доступ к хранимому значению объекта через значение gl, отличное от одного из следующих типов, поведение не определено:

  • динамический тип объекта,
  • cv-квалифицированная версия динамического типа объекта,
  • тип, аналогичный (как определено в 4.4) динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим cv-квалифицированной версии динамического типа объекта,
  • агрегатный тип или тип объединения, который включает один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных подагрегата или содержащегося объединения),
  • тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
  • тип char или unsigned char.
person David Rodríguez - dribeas    schedule 27.09.2012
comment
-1 По-твоему, почему в языке новое размещение? - person Cheers and hth. - Alf; 27.09.2012
comment
Что ж, так уж получилось, что с размещением нового наиболее распространенного использования вы в конечном итоге получаете доступ к символьному буферу через выражение lvalue другого типа. что было бы невозможно, если бы ваше утверждение, что вы не можете получить доступ к char через указатель на int, было истинным. что подразумевает, что ваше утверждение неверно (именно поэтому я проголосовал против: не то чтобы мне это не понравилось, но это просто неверно, логически это не может быть правдой) - person Cheers and hth. - Alf; 27.09.2012
comment
@Cheersandhth.-Alf: новое размещение передает буфер (void*) функции распределителя. Затем функция распределителя использует этот буфер для создания объекта. Обратите внимание, что в соответствии с правилами времени жизни в этот момент времени char объектов в буфере заканчивается их время жизни. Это не псевдоним. new (c) int(5) создает целое число со значением 5 в буфере. Буфер теперь int. reinterpret_cast<int*>(c) = 5 псевдонимы символов в буфере, как если бы они были int. Если вы не утверждаете, что reinterpret_cast<int*>(c) и new (c) int эквивалентны... - person David Rodríguez - dribeas; 27.09.2012
comment
вы утверждаете, что действие во время выполнения изменяет статический тип переменной. это чушь. - person Cheers and hth. - Alf; 27.09.2012
comment
@Cheersandhth.-Alf: Я никогда не утверждал, что правила псевдонимов (опять же, найдите время, чтобы прочитать §3.10/10) касаются не статических, а динамических тип объекта. Опять же, если я ошибаюсь, поправьте меня -- цитатой из стандарта, а не только мнениями. - person David Rodríguez - dribeas; 27.09.2012
comment
Это неверно ... Вы можете получить доступ к int через указатель на char Если последнее верно, то почему оно неверно? - person ildjarn; 27.09.2012
comment
@ildjarn: вы можете получить доступ к int через указатель на char, но вы не можете получить доступ к char через указатель на int. В приведенном выше коде объектами в памяти являются char, а reinterpret_cast<int*> создает псевдоним типа int*. Вы не можете использовать int* для доступа к (или нескольким) char объектам. - person David Rodríguez - dribeas; 27.09.2012
comment
А, понял теперь. (Я нахожу конкретную формулировку в вашем ответе сбивающей с толку.) - person ildjarn; 27.09.2012
comment
@David: в своем ответе вы написали, что не можете получить доступ к char [массиву] через указатель на int. Теперь вы сами утверждаете, что это утверждение неверно, когда массив символов динамически содержит представление типа int. Вы знаете, утверждение, что ваше собственное утверждение неверно, смехотворно в качестве аргумента в пользу его правильности. - person Cheers and hth. - Alf; 27.09.2012
comment
@Cheersandhth.-Alf: Вы сами добавили [массив], а я нет. Если вы хотите, чтобы я полностью объяснил это: вы не можете получить доступ к char object через указатель на int Дело в том, что в коде массив char содержит char объектов, и вы не можете получить доступ к к ним (объектам) через указатель на int. Если бы в коде было новое размещение, то int закончило бы время жизни объектов char в соответствии с правилами времени жизни, поскольку память повторно используется для другого объекта, и в этот момент вы получили бы доступ к объекту int, хранящемуся в массив char. - person David Rodríguez - dribeas; 27.09.2012
comment
@Cheersandhth.-Alf: Опять же, ваше мнение против формулировки стандарта. Если я не истолковал их неправильно, в этом случае, пожалуйста, скажите мне, что подразумевает стандарт, предоставьте цитату, которая противоречит тому, что я сказал. Я буду первым, кто удалит ответ, если он неправильный. Кстати, я считаю ваш последний комментарий оскорбительным. Если у вас есть факты, расскажите, если нет, по крайней мере, избегайте грубости. Что в цитате стандарта выше непонятно? (Это был бы конструктивный поступок, а не троллинг) - person David Rodríguez - dribeas; 27.09.2012
comment
@David: Это ваше мнение против вашего мнения, поскольку ваше мнение (о правильной интерпретации стандарта) противоречиво. Обычно вы должны быть в состоянии сделать вывод из внутреннего противоречия, что ваше мнение неверно. Обвинение ad hominem в грубости, следующее прямо по пятам за утверждением, что ваше собственное утверждение в вашем ответе было бессмысленным, а не неверным, само по себе следует за смехотворным утверждением, что ваше собственное утверждение было неверным и, следовательно, должно быть правильным, ну, это указывает что я не смогу помочь просветить вас по этому вопросу. - person Cheers and hth. - Alf; 27.09.2012
comment
+1, я действительно считаю, что единственный способ изменить динамический тип объекта, хранящегося в каком-либо месте (хранилище, занятое объектом), - это повторно использовать хранилище (завершив время жизни предыдущего объекта, занимающего это хранилище, и начать время жизни объекта). новый объект). Единственный способ повторно использовать хранилище таким образом — это разместить new. Эта последняя часть, к сожалению, не прописана в стандарте (по крайней мере, я не смог ее найти), но все примеры повторного использования в стандарте относятся к размещению new. - person mitchnull; 27.09.2012
comment
@Cheersandhth.-Alf: Вы, должно быть, неправильно поняли то, что я сказал, поскольку от ответа до самого последнего комментария нет никакого противоречия. Но вместо того, чтобы пытаться аргументировать свою позицию, вы просто ищете оправдания. Я еще не нашел никаких аргументов на вашей стороне, только оправдания: Возможно, я не смогу помочь вам просветить вас по этому вопросу. Простой вопрос (в третий раз): Определяет ли приведенная выше цитата, правильный или неправильный код в вопросе? Это простое да/нет с добавлением того, какая запись в списке поддерживает ваш претензии, если вы считаете это действительным кодом. - person David Rodríguez - dribeas; 27.09.2012
comment
@Cheersandhth.-Alf: Дело в том, что C++ является динамическим языком по вашему определению, то есть это язык, в котором динамический тип объекта имеет эффект (обратите внимание на многократное использование термина динамический тип во всем стандарте). В отличие от, скажем, Python, если вы пытаетесь получить доступ к объекту через неправильный динамический тип, ваш код имеет неопределенное поведение (а не создает ошибку времени выполнения), даже в этом случае концепция динамического типа требуется, чтобы рассуждать о программе C++ . - person Mankarse; 20.05.2014
comment
@Mankarse: Здесь обсуждалось логически невозможное утверждение Дэвида о том, что «вы не можете получить доступ к char через указатель на int». И все это зависело от того, что стандарт не предоставляет ни одного конкретного примера, а вместо этого использует memcpy (IIRC) в качестве примера. В этом контексте термин «динамический тип», используемый Священным стандартом, относится к исходному типу объекта, типу, используемому для создания битов в памяти, и не относится к типу среды выполнения. информация, например, Питон. Тем не менее, я не знаю, поддерживает ли это вашу точку зрения или противоречит ей. Очень непонятно, что вы имеете в виду. - person Cheers and hth. - Alf; 20.05.2014
comment
@Mankarse: независимо от того, выражали ли вы несогласие или согласие, это может помочь вам узнать о более широком метаконтексте, а именно о том, является ли компилятор g++ разумным и практичным, и является ли термин «строгий псевдоним» изобретением энтузиастов этого компилятора или он определен стандартом (это не так). Я считаю, что именно этот неупомянутый аспект и является причиной того, что в этом так много тепла. По моему опыту, всякий раз, когда этот компилятор потенциально выставляется в плохом свете (или, если уж на то пошло, всякий раз, когда, например, MSVC или Python потенциально выставляются в плохом свете), фанаты спешат на помощь. - person Cheers and hth. - Alf; 20.05.2014
comment
@Cheersandhth.-Alf: Это определенно правда, что есть части стандарта C++, которые ужасно косвенны или даже просто двусмысленны (даже для очень простых вещей, таких как лексическая грамматика), я думаю, это обязательно вызовет разногласия. Тем не менее, я не вижу ничего нелогичного в утверждении, что для использования массива char в качестве int вы должны сначала построить в нем int (с, например, размещением new). - person Mankarse; 20.05.2014
comment
Тем не менее, кажется, что [basic.life] имеет несколько возможных интерпретаций, в том числе ту, в которой время жизни int начинается, как только будет создан и выровнен массив char соответствующего размера (и поэтому время жизни int начнется до любого размещения нового выражения) . - person Mankarse; 20.05.2014
comment
@Mankarse: проблема с этой интерпретацией заключается в том, что это означает, что необработанный фрагмент памяти (char [4]) будет иметь не один тип, а бесконечное количество типов: все возможные POD занимающий 4 или менее символов размера. Это противоречит стандартному использованию типа the (single) при обращении к объекту. Язык четко документирует, что new используется для создания объекта и состоит из двух этапов: выделения и инициализации. Время жизни, начинающееся с выделения, означает, что даже если инициализация/конструкция не оцениваются, объект существует. - person David Rodríguez - dribeas; 20.05.2014
comment
@DavidRodríguez-dribeas: вы что-то поняли: вы обнаружили несоответствие, как вы говорите, «конфликтует» в вашей интерпретации вещей. - person Cheers and hth. - Alf; 20.05.2014
comment
@Mankarse: утверждение Дэвида в этом ответе даже не зависит от того, действителен ли битовый шаблон для результирующего объекта, на который делается ссылка, это просто прямое «вы не можете получить доступ к char через указатель на int». Это утверждение несовместимо с вашей точкой зрения: «Я не вижу ничего нелогичного в утверждении, что для использования массива символов в качестве целого числа вы должны сначала создать в нем целое число (с, например, новым размещением)», и оно несовместимо с его собственные комментарии. Путаница в отношении формы единственного числа (он использует ее в отношении массива) по сравнению с формой множественного числа — это просто внутреннее противоречие. Это накапливается. - person Cheers and hth. - Alf; 20.05.2014
comment
@Cheersandhth.-Alf: Теперь я понимаю, что ты говоришь. Суть этого разногласия, по-видимому, заключается в том, что Дэвид утверждает, что область памяти может иметь только один динамический тип в любой момент выполнения программы, в то время как вы считаете, что даже если int может быть создан в char[], char[] по-прежнему представляет собой массив из char объектов. - person Mankarse; 20.05.2014
comment
По мнению Дэвида, после создания int память больше не содержит char, и поэтому возможность чтения исходного буфера char[] как массива char обеспечивается только тем фактом, что всегда разрешено читать любой объект как char[]. (независимо от его динамического типа, то есть с помощью правила «алиасинга» из §3.10/10). Представление Дэвида приводит к таким утверждениям, как вы не можете получить доступ к char через указатель на int; что особенно важно, как только int создается в буфере, буфер больше не содержит объектов char (и поэтому теперь вы можете получить доступ к int). - person Mankarse; 20.05.2014
comment
По вашему мнению, char[] по-прежнему представляет собой последовательность объектов char, а также содержащих int, и поэтому вы считаете, что очевидно можно получить доступ к char через int. - person Mankarse; 20.05.2014
comment
да, я думаю, что это все, или основная часть этого. ;-) ну, за исключением того, что утверждение в этом ответе не зависит от того, построен ли там int или нет вообще. - person Cheers and hth. - Alf; 20.05.2014
comment
@Cheersandhth.-Alf: Да, но если в какой-то области памяти был создан int, то эта память больше не содержит char. Вместо этого char — это просто альтернативный (и законный) способ доступа к int, хранящемуся в области памяти. (по крайней мере, согласно интерпретации стандарта Дэвидом). - person Mankarse; 20.05.2014
comment
С такой точкой зрения можно было бы легче и яснее не согласиться, да. Например. возможность и практическая необходимость доступа к несуществующим объектам. К сожалению, это мнение было выражено только в комментариях, и оно категорически противоречит утверждению в ответе. - person Cheers and hth. - Alf; 20.05.2014
comment
@Mankarse: За исключением того, что в тот момент времени, когда вы используете новое размещение с int, стандарт четко говорит, что объекты char перестают существовать: Срок жизни объекта типа T заканчивается, когда: [...] память, которую занимает объект, повторно используется или освобождается. Другие части могут быть интерпретированы, но это правило совершенно ясно, не так ли? Вы по-прежнему можете получить к нему доступ (прочитать) через char*, поскольку стандарт предоставляет эту специальную гарантию, но попытка записи через char* является нарушением правил псевдонимов (назовите это строгие правила псевдонимов или как хотите) - person David Rodríguez - dribeas; 20.05.2014
comment
Ответ построен на предположении, что если вы называете его массивом символов, это потому, что это такой зверь, а не int, построенный поверх него. Но это все отвлечение от вопроса: законно ли это в стандарте? Нет. Есть сомнения в том, что это законно? Есть ли сомнения, что после построения int объект char перестает существовать? Есть ли сомнения в том, что правила алиасинга позволяют читать char через int*? - person David Rodríguez - dribeas; 20.05.2014
comment
@DavidRodríguez-dribeas: Чтобы было ясно, я согласен с вашей интерпретацией. - person Mankarse; 20.05.2014

Что касается законности…

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;

И давайте рассмотрим только случай, когда компилятор гарантирует полезное значение указателя, которое поместит указатель в те же байты памяти (причина, по которой это зависит от компилятора, заключается в том, что стандарт в §5.2.10/7 только гарантирует это для преобразований указателя, где типы совместимы с выравниванием, и в противном случае оставляет его как неопределенное (но тогда весь §5.2.10 несколько несовместим с §9.2/18).

Теперь одна интерпретация §3.10/10 стандарта, так называемая оговорка о строгом псевдониме (но обратите внимание, что в стандарте никогда не используется термин строгое псевдоним),

Если программа пытается получить доступ к хранимому значению объекта через значение gl, отличное от одного из следующих типов, поведение не определено:

  • динамический тип объекта,
  • cv-квалифицированная версия динамического типа объекта,
  • тип, аналогичный (как определено в 4.4) динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим динамическому типу объекта,
  • тип, который является подписанным или беззнаковым типом, соответствующим cv-квалифицированной версии динамического типа объекта,
  • агрегатный тип или тип объединения, который включает один из вышеупомянутых типов среди своих элементов или нестатических элементов данных (включая, рекурсивно, элемент или нестатический элемент данных подагрегата или содержащегося объединения),
  • тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
  • тип char или unsigned char.

заключается в том, что, как говорится в нем самом, речь идет о динамическом типе объекта, находящегося в c байтах.

С этой интерпретацией операция чтения в *p выполняется нормально, если foo поместил туда объект int, а в противном случае - нет. Таким образом, в этом случае доступ к массиву char осуществляется через указатель int*. И никто не сомневается, что другой способ допустим: хотя foo мог поместить int объект в эти байты, вы можете свободно обращаться к этому объекту как к последовательности char значений, последним тире в §3.10/10.

Таким образом, с этой (обычной) интерпретацией после того, как foo поместил туда int, мы можем получить к нему доступ как к объектам char, поэтому в области памяти с именем c существует по крайней мере один объект char; и мы можем получить к нему доступ как int, так что, по крайней мере, этот int тоже существует; и поэтому утверждение Дэвида в другом ответе о том, что к объектам char нельзя получить доступ как int, несовместимо с этой обычной интерпретацией.

Утверждение Дэвида также несовместимо с наиболее распространенным использованием слова new.

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

Итак, в заключение, что касается Holy Standard, простое приведение к массиву указателя T* практически полезно или не зависит от компилятора, а доступ к указанному потенциальному значению действителен или нет в зависимости от того, что присутствует . В частности, подумайте о ловушке-представлении int: вы бы не хотели, чтобы это взорвалось на вас, если бы битовый шаблон оказался таким. Так что, чтобы быть в безопасности, вы должны знать, что там внутри, биты, и, как показывает вызов foo выше, компилятор вообще может не знать, что например, оптимизатор на основе строгого выравнивания компилятора g++ может в общем не знаю что…

person Cheers and hth. - Alf    schedule 27.09.2012
comment
Вы не поняли, что я сказал. Я утверждаю, что к объекту char нельзя получить доступ через указатель int. Ваше утверждение состоит в том, что как только вы замените объект char объектом int посредством размещения new, вы сможете получить доступ к объекту int через указатель int. Вы не получаете доступ к char через указатель int, вы получаете доступ к int, который был создан в той же ячейке памяти, которая ранее удерживалась (несколько) объектами char. Учиться читать. - person David Rodríguez - dribeas; 27.09.2012
comment
@David: когда ты говоришь научиться читать, это хорошая идея. кажется, вы страдаете от некоторого неправильного представления о том, что c++ похож на python (скажем), язык с динамической типизацией. это не так: эти байты не наделены связанной информацией о динамическом типе, и, кроме того, доступ к объектам char означает, что они есть (вы не можете очень хорошо получить доступ к тому, чего там нет). короче логика у вас отсутствует напрочь. возможно из-за фундаментального заблуждения, как я сейчас обрисовал, и я очень надеюсь, что это так. - person Cheers and hth. - Alf; 27.09.2012
comment
Итак, ваше утверждение правильное или неправильное (предположим, 32-битное целое): float f; int* p = new (&f) int(1); cout << *p;. А если неправильно, то почему? Обратите внимание, что я использовал dynamic не в смысле хранения информации RTTI, а в качестве фактического типа объекта в ячейке памяти. Есть ли новый объект float после размещения? (Если вы думаете, что да, прочитайте главу о времени жизни объекта в стандарте) Законно ли делать cout << f после размещения new? Чего вы не видите, так это того, что вы говорите то же самое, что и я, но не видите, что это одно и то же. - person David Rodríguez - dribeas; 27.09.2012
comment
@david: изобретать все больше и больше вечных двигателей в стиле голдберга не заставит меня даже взглянуть на это, потому что (1) законы физики говорят, что независимо от искаженной внутренней логики это неправильно, и (2) я знаю что вы это знаете. - person Cheers and hth. - Alf; 27.09.2012
comment
Ваш последний комментарий многое говорит о вас. Мне жаль, что ты такой, но я не могу сделать тебя лучше. - person David Rodríguez - dribeas; 27.09.2012
comment
@david: я понимаю, почему ты снова переходишь на личности. - person Cheers and hth. - Alf; 27.09.2012
comment
@downvoter: пожалуйста, объясните, что вы считаете неправильным в этом ответе, чтобы другие могли игнорировать ваш отрицательный голос. Тиа. - person Cheers and hth. - Alf; 27.09.2012
comment
:) Ваш последний комментарий поучительный. Вы ведь знаете, что означает ad hominem? Это означает избегать спора и сосредоточиться на личном недостатке. Мой последний комментарий не был уклонением от аргумента, вы уже делали это раньше. Этот комментарий является просто констатацией истины (мнением, если хотите). Вы избегаете спорить о приведенном мной примере и утверждаете, что можете читать мои мысли (с ограниченным успехом, если честно... что вы знаете, что < b>поэтому не по адресу) Итак, нет, вы не понимаете, почему [I] иду к ad hominem, поскольку эта предпосылка ложна. - person David Rodríguez - dribeas; 27.09.2012
comment
@David: с таким количеством личных нападок и инсинуаций, таких как ваша цитата Альфа о стандарте в вашем ответе (который, как вы знали, совершенно не нужен и вводит читателей в заблуждение), я бы счел удивительным, что по крайней мере два человека поддерживают вас анонимно отрицая мой правильный ответ и поддерживая ваши бессмысленные комментарии. но я потерял всякую веру в людей в целом. некоторые люди хорошие, большинство людей нет. :-( - person Cheers and hth. - Alf; 28.09.2012
comment
Я действительно не верю, что люди голосуют против, чтобы поддержать другой ответ, тем более, учитывая, что я не получил эквивалентных голосов. С другой стороны, есть кто-то, кто проголосовал за некоторые комментарии. Иди разберись. - person David Rodríguez - dribeas; 28.09.2012
comment
@anonymous downvoter: пожалуйста, объясните свой отрицательный голос. Я предполагаю, что это из-за бездумного сканирования комментариев и/или фанатизма gcc. однако было бы неплохо, если бы хотя бы один даунвотер мог продемонстрировать какое-то рациональное мышление. - person Cheers and hth. - Alf; 23.12.2014
comment
@DavidRodríguez-dribeas, это обсуждение было грустным человеком. Я здесь с Альфом. Кричать «Учись читать» — это, конечно, ad hominen, это не часть здорового дискурса. Также просите пользователя читать ваши мысли, когда вы утверждаете, что просто фрагмент приведения неверен, хотя он действительно действителен, и ответ Альфа помогает избежать этой путаницы, а затем говорит людям научиться читать? Может быть, мы умеем читать, но вы не умели писать в то время. - person pepper_chico; 01.02.2017