Подтвердить что ты не робот

GCC: точность строгих предупреждений о сглаживании

Я пытаюсь проверить некоторые из моих кодов для строгих нарушений псевдонимов, но это похоже, что я что-то пропустил, пытаясь понять строгий псевдоним Правило.

Представьте следующий код:

#include <stdio.h>

int main( void )
{
    unsigned long l;

    l = 0;

    *( ( unsigned short * )&l ) = 1;

    printf( "%lu\n", l );

    return 0;
}

Классический и базовый пример. С GCC 4.9 (-Wall -fstrict-aliasing -Wstrict-aliasing -O3) он сообщает об ошибке:

 dereferencing type-punned pointer will break strict-aliasing rules

Но следующие компилируются отлично:

#include <stdio.h>

int main( void )
{
    unsigned long    l;
    unsigned short * sp;

    l       = 0;
    sp      = ( unsigned short * )&l;
    *( sp ) = 1;

    printf( "%lu\n", l );

    return 0;
}

В моем понимании, второй пример также нарушает правило сглаживания структуры.
Так почему он компилируется? Это вопрос точности в GCC, или я пропустил что-то со строгим псевдонимом?

Я также нашел следующий раздел: Почему в этом коде не генерируются строгие сглаживания?

Компиляция с -Wstrict-aliasing=2 или -Wstrict-aliasing=3 не имеет значения.
Но -Wstrict-aliasing=1 сообщает об ошибке во втором примере.

Документация GCC говорит, что уровень 1 является наименее точным и может привести к многому ложных срабатываний, тогда как уровень 3 является наиболее точным...

Итак, что здесь происходит? Проблема с моим собственным пониманием или проблемой с GCC?

Бонусный вопрос

Я обычно предпочитаю Clang/LLVM над GCC для моих проектов, но, похоже, Clang не выдает предупреждения о строгом псевдониме.
Кто-нибудь знает, почему? Это потому, что он не способен обнаруживать нарушения, или потому, что он не следует правило при генерации кода?

4b9b3361

Ответ 1

Ваше понимание верное. Анализ псевдонимов, как правило, сложный, и в этом случае, по-видимому, простое использование временного указателя между литой и разыменованием было достаточно, чтобы выбросить его. Удивительно, но GCC 4.8.2 делает лучшую работу над этим кодом, предупреждая при -Wstrict-aliasing=2, а также уровень 1, поэтому это регрессия.

Что касается clang, у него просто нет возможности предупреждать о нарушениях псевдонимов. Он абсолютно использует правило в оптимизации. Чтобы увидеть это в действии, возьмите этот пример прямо из стандарта C (N1570 §6.5.2.3 9))

struct t1 { int m; };
struct t2 { int m; };

int f(struct t1 *p1, struct t2 *p2) {
    if (p1->m < 0)
        p2->m = -p2->m;
    return p1->m;
}

Если p1 и p2 указывают на одну и ту же структуру, Clang (и GCC), тем не менее, возвращают значение p1->m перед отрицанием, так как они могут считать p2 не псевдоним p1, и поэтому предыдущее отрицание никогда не влияет на результат. Здесь полный пример и вывод с и без -fstrict-aliasing. Дополнительные примеры см. В здесь и часто цитируемый Что должен каждый программист C Знать о Undefined Поведение; строгая оптимизация псевдонимов - последняя тема вводного сообщения.

Что касается того, когда будут реализованы предупреждения, разработчики будут тихими, но они упоминаются в наборе тестов clang, в котором перечислены -Wstrict-aliasing=X под заголовком (выделение мое)

Эти флаги в настоящее время не реализованы; проверить, что мы их выводим в любом случае.

Таким образом, это может произойти в какой-то момент.

Ответ 2

В

нет ничего неправильного.
sp      = ( unsigned short * )&l;

Для всего компилятора вы можете просто снова вернуть указатель к правильному типу.

В

также нет ничего неправильного.
*( sp ) = 1;

Это комбинация двух неправильных.

Компилятор просто не может поместить двух вместе, чтобы сказать вам, что комбинация проблематична (в общем случае это невозможно, и в данном конкретном случае это не так просто).

Но

*( ( unsigned short * )&l ) = 1;

намного проще обнаружить, и, следовательно, компилятор делает все возможное для этого.

Ответ 3

Я ожидаю, что в O3 компилятор решит, что, поскольку он знает, что переменные разных типов не могут быть псевдонимом, sp никогда не используется. Поэтому он удаляет sp из программы и не генерируется ошибка.

Либо это, либо потому, что l является известным значением, оно преобразуется в константу для использования printf. Он по-прежнему будет иметь хранилище, потому что его адрес взят, но это хранилище никогда не записывается до тех пор, пока оно не известно.

Ответ 4

Моя интерпретация ситуации:

  • *( ( unsigned short * )&l ) = 1; легче поймать, потому что нарушение находится в одной строке. Поэтому он всегда попадал.

  • С оптимизацией вторая вариация также улавливается. После оптимизации он прост, как первый.

  • Без оптимизации -Wstrict-aliasing=1, который является более ленивым, чем значение по умолчанию, ловит его. Это связано с тем, что он воспринимает актерский состав как нарушение, даже без разыменования (удалите разыменование и посмотрите). Эта упрощенная логика быстрая, но приводит к ложным срабатываниям.