Правильно ли это строгий пример псевдонимов?

Я читал о строгих правилах псевдонимов за последнюю неделю или около того и наткнулся на эту статью: Понимание C/С++ Strict Aliasing.

В статье рассмотрены несколько способов обмена двумя половинами 32-разрядного целого числа, что дает как хорошие примеры, так и те, которые нарушают правило строгого сглаживания. Однако мне трудно понять один из примеров.

Этот код описан как сломанный.

uint32_t
swaphalves(uint32_t a)
{
    a = (a >> 16) | (a << 16);
    return a;
}

Приведенная причина:

Эта версия выглядит разумно, но вы не знаете, есть ли правая и левая стороны | каждый получит исходную версию a или если один из них получит результат другой. Здесь нет точки последовательности, поэтому мы ничего не знаем о порядке операции, и вы можете получать разные результаты от одного и того же компилятора, используя разные уровни оптимизации.

Я не согласен. Этот код выглядит хорошо для меня. В строке a = (a >> 16 | (a << 16); есть только одна запись в a, и я ожидаю, что оба чтения a пройдут до этой записи. Кроме того, нет указателей или ссылок и нет несовместимых типов.

Мне не хватает строгого нарушения псевдонимов в этом коде или неверна статья?

Ответ 1

В этом коде нет указателей и ссылок, поэтому строгие правила псевдонимов даже не входят в изображение. И действительно, автор вызывает точки последовательности, а не строгий псевдоним, чтобы оправдать утверждение, что оно undefined. Однако, похоже, это рассуждение неверно, и фрагмент кода имеет совершенно определенную семантику. Как Prasoon Saurav объясняет более подробно:

(§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;
}

было поведение undefined из-за точек последовательности,

uint32_t
swaphalves(uint32_t a)
{
    return (a >> 16) | (a << 16);
}

не будет (записи не будут упорядочены), и поэтому гораздо более сложные версии (union, memcpy), которые занимают большую часть остальной части статьи, бессмысленны.