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