Есть ли у вас ужасные истории? Руководство GCC недавно добавило предупреждение о -fstrict-aliasing и литье указателя через объединение:
[...] Принимая адрес, вызывая результирующий указатель и разыменовывая результат, имеет undefined поведение [выделение добавлено], даже если cast использует тип объединения, например:
union a_union {
int i;
double d;
};
int f() {
double d = 3.0;
return ((union a_union *)&d)->i;
}
Есть ли у кого-нибудь пример для иллюстрации этого поведения undefined?
Обратите внимание, что этот вопрос не касается того, что говорит стандарт 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
while 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)
И функции также встроены (что, по-видимому, является причиной некоторых тонких ошибок с строгим сглаживанием), но это не имеет значения. (И этот ассемблер не так уместен, но он добавляет убедительные детали.)
Также обратите внимание, что прием адресов явно неверен (или правильно, если вы пытаетесь проиллюстрировать поведение undefined). Например, как мы знаем, это неверно:
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/jtc1/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 строгие правила псевдонимов в С++ (GCC)
В первой ссылке, проект протокола собрания ISO семь месяцев назад, один участник отмечает в разделе 4.16:
Есть ли кто-нибудь, кто считает, что правила достаточно ясны? Никто не может их интерпретировать.
Другие примечания: Мой тест был с gcc 4.3.4, с -O2; опции -O2 и -O3 подразумевают -fstrict-aliasing. В примере из руководства GCC предполагается sizeof (double) > = sizeof (int); неважно, являются ли они неравными.
Кроме того, как отметил Майк Актон в ссылке cellperformace, -Wstrict-aliasing=2
, но не =3
, для примера здесь warning: dereferencing type-punned pointer might break strict-aliasing rules
.