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 не выдает никаких предупреждений о строгом псевдониме.
Кто-нибудь знает, почему?
Это потому, что он не может обнаруживать нарушения, или потому, что он не соблюдает правила при генерации кода?


person Macmade    schedule 19.01.2014    source источник
comment
Для меня это действительно нарушает его, потому что компилятор может предположить, что значение l является постоянным во втором примере.   -  person Macmade    schedule 19.01.2014
comment
Проблема с нарушениями псевдонима заключается в том, что их трудно обнаружить, в зависимости от контекста это может быть даже невозможно без запуска самого кода. Вот почему это не нарушение ограничения (ошибка, которую должен обнаруживать каждый компилятор), а поведение просто становится неопределенным просто потому, что было бы кошмаром написать оптимизирующий компилятор, который мог бы гарантировать что-то по этому поводу. Есть очень мало веских причин для программирования с таким приведением указателя и не использовать промежуточное union или приведение к unsinged char*.   -  person Jens Gustedt    schedule 19.01.2014
comment
Здесь -Wstrict-aliasing = 1 дает предупреждение с помощью may, -Wstrict-aliasing = 2 дает предупреждение с помощью will и -Wstrict-aliasing = 3 не дает предупреждения.   -  person AProgrammer    schedule 19.01.2014
comment
@Macmade Вы ничего не упускаете, компилятор недостаточно умен. Второй пример - это, конечно же, нарушение семантики.   -  person    schedule 19.01.2014
comment
Ваш вопрос касается чувствительности предупреждений GCC о строгом псевдониме, но если вы уже думали об этом таким образом, вы, вероятно, не задали бы вопрос: en.wikipedia.org/wiki/Sensitivity_and_specificity   -  person Pascal Cuoq    schedule 19.01.2014


Ответы (4)


Ваше понимание правильное. Анализ псевдонимов обычно сложен, и в этом случае, очевидно, простого использования временного указателя между приведением и разыменованием было достаточно, чтобы его сбросить. Удивительно, но 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 должен знать о неопределенном поведении; строгая оптимизация псевдонимов - последняя тема вводного поста.

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

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

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

person tab    schedule 19.01.2014
comment
Большое спасибо за заметку о Clang. Могу я спросить, как вы это узнали? - person Macmade; 19.01.2014
comment
@Macmade добавил несколько деталей и ссылок. - person tab; 19.01.2014
comment
+1. Но для уточнения, может быть, изменить фразу return p1->m; на что-то вроде возврата начального значения p1->M? - person Joseph Quinsey; 09.04.2014
comment
@JosephQuinsey Спасибо, то, как я это написал, на самом деле вводило в заблуждение. - person tab; 09.04.2014

В принципе нет ничего плохого в

sp      = ( unsigned short * )&l;

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

Также нет ничего плохого в принципе

*( sp ) = 1;

Это неправильное сочетание двух.

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

Но

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

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

person user541686    schedule 19.01.2014
comment
Спасибо, значит, второй пример все еще нарушает правило, даже если не обнаружен? - person Macmade; 19.01.2014
comment
@Macmade: Да, введение новой переменной не приводит к внезапным изменениям. - person user541686; 19.01.2014

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

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

person Zan Lynx    schedule 19.01.2014
comment
Спасибо, и, кстати, очень хорошая догадка. Но, к сожалению, я попытался использовать sp, но предупреждения все еще не было. - person Macmade; 19.01.2014
comment
@Macmade: Тогда это потому, что l - известное значение, которое преобразуется в константу и удаляется из программы. - person Zan Lynx; 19.01.2014
comment
Ну, объявление его как volatile, к сожалению, ничего не меняет ... Но я думаю, что @Mehrdad все правильно понял ... В любом случае спасибо за ответ :) - person Macmade; 19.01.2014

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

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

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

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

person ugoren    schedule 19.01.2014