Еще раз: строгое правило псевдонимов и char *
Чем больше я читаю, тем больше я смущаюсь.
Последний вопрос из близких ближе всего к моему вопросу, но я запутался со всеми словами об объекте жизни и особенно - нормально ли читать или нет.
Чтобы добраться до точки. Исправьте меня, если я ошибаюсь.
Это нормально, gcc не дает предупреждения, и я пытаюсь "читать тип T
(uint32_t
) через char*
":
uint32_t num = 0x01020304;
char* buff = reinterpret_cast< char* >( &num );
Но это "плохо" (также дает предупреждение), и я пытаюсь "по-другому":
char buff[ 4 ] = { 0x1, 0x2, 0x3, 0x4 };
uint32_t num = *reinterpret_cast< uint32_t* >( buff );
Как второй отличается от первого, особенно когда мы говорим о инструкциях по переупорядочению (для оптимизации)? Кроме того, добавление const
никак не изменит ситуацию.
Или это просто правильное правило, в котором четко говорится: "Это можно сделать в одном направлении, но не в другом"?
Я не мог найти ничего в стандартах (искал это особенно в стандарте С++ 11).
Это то же самое для C и С++ (как я прочитал комментарий, подразумевая, что он отличается для двух языков)?
Я использовал union
для "обхода" этого, который по-прежнему кажется НЕ 100% OK, поскольку он не гарантируется стандартом (в котором говорится, что я могу полагаться только на значение, последний раз модифицированный в union
).
Итак, прочитав много, я теперь более смущен. Я думаю, что только memcpy
является "хорошим" решением?
Похожие вопросы:
ИЗМЕНИТЬ
Ситуация в реальном мире: у меня есть сторонняя библиотека (http://www.fastcrypto.org/), которая вычисляет UMAC, а возвращаемое значение находится в char[ 4 ]
. Затем мне нужно преобразовать это в uint32_t
. И, кстати, lib часто использует такие вещи, как ((UINT32 *)pc->nonce)[0] = ((UINT32 *)nonce)[0]
. Так или иначе.
Кроме того, я спрашиваю, что правильно, а что не так и почему. Не только о переупорядочении, оптимизации и т.д. (Интересно то, что с -O0
нет предупреждений, только с -O2
).
И обратите внимание. Я знаю о большой/маленькой ситуации. Это не так. Я действительно хочу проигнорировать это утверждение. "Строгие правила псевдонимов" звучат как что-то действительно серьезное, гораздо более серьезное, чем неправильное утверждение. Я имею в виду - например, доступ к/изменению памяти, которая не должна быть затронута; любой вид UB вообще.
Цитаты из стандартов (C и С++) будут действительно оценены. Я ничего не мог найти о правилах псевдонимов или что-то в этом роде.
Ответы
Ответ 1
Как второй отличается от первого, особенно когда мы говорим о инструкциях по переупорядочению (для оптимизации)?
Проблема заключается в компиляторе, используя правила, чтобы определить, разрешена ли такая оптимизация. Во втором случае вы пытаетесь прочитать объект char[]
через несовместимый тип указателя, который является undefined поведением; следовательно, компилятор может переупорядочить чтение и запись (или сделать что-нибудь еще, чего вы не ожидаете).
Как неестественно, как может показаться, вам действительно нужно перестать думать о том, как вы думаете, что компилятор может оптимизировать и просто соблюдать правила.
Или это просто правильное правило, в котором четко говорится: "Это можно сделать в одном направлении, но не в другом"? Я не мог найти ничего в стандартах (искал это особенно в стандарте С++ 11).
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf глава 3.10, пункт 10.
В C99, и я думаю, что также C11, это 6.5 пункт 7.
Оба C и С++ разрешают доступ к любому типу объекта с помощью char *
(или, в частности, lvalue типа char
). Они не разрешают доступ к объекту char
через произвольный тип. Так что да, правило является правилом "один путь".
Я использовал union для "обхода" этого, который по-прежнему кажется НЕ 100% ОК, поскольку он не гарантируется стандартом (который утверждает, что я могу полагаться только на значение, которое в последний раз модифицировано в объединении).
Хотя формулировка стандарта ужасно неоднозначна, в C99 (и за ее пределами) ясно (по крайней мере, с C99 TC3), что цель состоит в том, чтобы разрешить пул через объединение. Тем не менее, вы должны выполнять все обращения через объединение (в частности, вы не можете просто "ввести союз в существование" для целей пиратства).
возвращаемое значение находится в char [4]. Затем мне нужно преобразовать это в uint32_t
Просто используйте memcpy
или вручную переместите байты в нужную позицию, если проблема с байтом. Хорошие компиляторы могут в любом случае оптимизировать это (да, даже вызов memcpy
).
Ответ 2
Я использовал union для "обхода" этого, который по-прежнему кажется НЕ 100% ОК, поскольку он не гарантируется стандартом (который утверждает, что я могу полагаться только на значение, которое в последний раз модифицировано в объединении).
Эндианс - причина этого. В частности, последовательность байтов 01 00 00 00
может означать 1 или 16 777 216.
Правильный способ сделать то, что вы делаете, - прекратить попытки обмануть компилятор для выполнения преобразования для вас и выполнить преобразование самостоятельно.
Например, если char[4]
имеет малоконечный (наименьший байт первым), вы делаете что-то вроде следующего.
char[] buff = new char[4];
uint32_t result = 0;
for (int i = 0; i < 4; i++)
result = (result << 8) + buff[i];
Это вручную выполняет преобразование между ними и гарантирует, что всегда будет правильно, когда вы выполняете математическое преобразование.
Теперь, если вы быстро делаете это преобразование, может иметь смысл использовать #if и знания вашей архитектуры, чтобы использовать перечисление, чтобы сделать это автоматически, как вы упомянули, но это снова выходит из портативных решений. (Также вы можете использовать что-то вроде этого в качестве резервного, если не можете быть уверены)