Является ли этот пример строгого сглаживания правильным?


Я читал о строгих правилах алиасинга на прошлой неделе или около того и наткнулся на эту статью: понимание строгого алиасинга 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 11

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), которые занимают большую часть остальной части статьи, бессмысленны.