У вас есть какие-нибудь страшные истории, чтобы рассказать? Руководство GCC недавно добавило предупреждение о -fstrict-aliasing и приведении указателя через объединение:
[...] Взятие адреса, приведение результирующего указателя и разыменование результата имеют неопределенное поведение [выделение добавлено], даже если приведение использует тип объединения, например :
union a_union {
int i;
double d;
};
int f() {
double d = 3.0;
return ((union a_union *)&d)->i;
}
У кого-нибудь есть пример, иллюстрирующий это неопределенное поведение?
Обратите внимание, что этот вопрос не о том, что говорит или не говорит стандарт C99. Сегодня речь идет о реальном функционировании gcc и других существующих компиляторов.
Я только догадываюсь, но одна потенциальная проблема может заключаться в настройке от d
до 3,0. Поскольку d
- это временная переменная, которая никогда не читается напрямую, и которая никогда не читается с помощью «несколько совместимого» указателя, компилятор может не пытаться установить ее. И тогда f () вернет мусор из стека.
Моя простая, наивная попытка не удалась. Например:
#include <stdio.h>
union a_union {
int i;
double d;
};
int f1(void) {
union a_union t;
t.d = 3333333.0;
return t.i; // gcc manual: 'type-punning is allowed, provided...' (C90 6.3.2.3)
}
int f2(void) {
double d = 3333333.0;
return ((union a_union *)&d)->i; // gcc manual: 'undefined behavior'
}
int main(void) {
printf("%d\n", f1());
printf("%d\n", f2());
return 0;
}
отлично работает, выдавая на CYGWIN:
-2147483648
-2147483648
Глядя на ассемблер, мы видим, что gcc полностью оптимизирует t
прочь: f1()
просто хранит предварительно рассчитанный ответ:
movl $-2147483648, %eax
, в то время как f2()
помещает 3333333.0 в стек с плавающей точкой и извлекает возвращаемое значение:
flds LC0 # LC0: 1246458708 (= 3333333.0) (--> 80 bits)
fstpl -8(%ebp) # save in d (64 bits)
movl -8(%ebp), %eax # return value (32 bits)
И функции также встроены (что, по-видимому, является причиной некоторых тонких ошибок со строгим псевдонимом), но здесь это не имеет значения. (И этот ассемблер не так уж важен, но он добавляет подтверждающие детали.)
Также обратите внимание, что взятие адресов явно неверно (или правильно , если вы пытаетесь проиллюстрировать неопределенное поведение). Например, как мы знаем, это неправильно:
extern void foo(int *, double *);
union a_union t;
t.d = 3.0;
foo(&t.i, &t.d); // undefined behavior
мы также знаем, что это неправильно:
extern void foo(int *, double *);
double d = 3.0;
foo(&((union a_union *)&d)->i, &d); // undefined behavior
Дополнительную информацию об этом см., Например:
http://www.open -std.org / ОТК1 / SC22 / WG14 / WWW / Docs / n1422.pdf
http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html
http://davmac.wordpress.com/2010/02/26/c99-revisited/
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
(= поиск страницы в Google , затем просмотр кэшированной страницы)
Что такое строгое правило наложения имен?
C99 строгие правила псевдонимов в C ++ (GCC)
В первой ссылке, черновик протокола собрания ИСО семь месяцев назад, один участник отмечает в разделе 4.16:
Кто-нибудь считает, что правила достаточно ясны? Никто на самом деле не может их интерпретировать.
Другие примечания: Мой тест был с gcc 4.3.4, с -O2; опции -O2 и -O3 подразумевают -fstrict-aliasing. Пример из руководства GCC предполагает sizeof (double) > = sizeof (int); не имеет значения, неравны ли они.
Кроме того, как отметил Майк Актон в ссылке cellperformace, -Wstrict-aliasing=2
, но not =3
, выдает warning: dereferencing type-punned pointer might break strict-aliasing rules
для примера здесь.