Является ли этот пример строгого сглаживания правильным?
Я читал о строгих правилах алиасинга на прошлой неделе или около того и наткнулся на эту статью: понимание строгого алиасинга C/C++ .
В статье рассматривается несколько способов замены половинок 32-битного целого числа, дающих как хорошие примеры, так и те, которые нарушают строгое правило псевдонимирования. Однако мне трудно понять один из примеров.Этот код описывается как сломанный.
uint32_t
swaphalves(uint32_t a)
{
a = (a >> 16) | (a << 16);
return a;
}
Приведенная причина такова:
Это версия выглядит разумной, но вы не знаете, если правая и левая стороны | получит ли каждый из них исходную версию
a
или если один из них получит результат другой. Здесь нет точки последовательности, поэтому мы ничего не знаем о порядке операции здесь, и вы можете получить разные результаты от одного и того же компилятора, используя разные уровень оптимизации.
Я не согласен. Этот код кажется мне прекрасным. Есть только одна запись в a
в строке a = (a >> 16 | (a << 16);
, и я ожидайте, что оба чтения a
происходят до этой записи. Кроме того, нет указателей или ссылок и несовместимых типов.
Я пропустил строгое нарушение алиасинга в этом кодексе, или статья неверна?
1 ответ:
В этом коде нет никаких указателей и ссылок, поэтому строгие правила псевдонимов даже не входят в картину. И действительно, автор ссылается наточки последовательности , а не на строгое алиасирование, чтобы оправдать утверждение, что оно неопределенно. Однако, похоже, это рассуждение неверно, и фрагмент кода имеет совершенно определенную семантику. Как Прасун Саурав объясняет более подробно :
(§1.9/15) вычисления значений операндов оператора являются секвенируется перед вычислением значения результата работы оператора.
Таким образом, в отношении оператора
=
Оценкаa
и(a >> 16) | (a << 16)
упорядочиваются перед назначением. Ни то, ни другое не является проблематичным: хотя все его части не упорядочены относительно друг друга, не остается записи вa
, которую нужно было бы упорядочить.(технически это поднимает вопрос о том, как секвенируется побочный эффект назначения, т. е. вычисление его значения, но я не смог найди что-нибудь по этому поводу. Вероятно, это где-то в стандарте, но у меня нет копии под рукой. Я сильно подозреваю, что он последователен после вычисления значения по причинам, приведенным в следующем абзаце.)
Вы также можете применить здравый смысл: запись в
a
должна сначала оценить(a >> 16) | (a << 16)
, чтобы записать правильное значение , и поэтому это не может произойти в середине этой оценки. Другая проблема со статьей заключается в том, что даже еслиuint32_t swaphalves(uint32_t a) { a = (a >> 16) | (a << 16); return a; }
Имел неопределенное поведение из-за точки последовательности,
uint32_t swaphalves(uint32_t a) { return (a >> 16) | (a << 16); }
Не будет (нет записей, которые нужно секвенировать), и поэтому гораздо более сложные версии (союзы, memcpy), которые занимают большую часть остальной части статьи, бессмысленны.