GCC, -O2 и бит-поля - это ошибка или функция?
Сегодня я обнаружил тревожное поведение при экспериментировании с битовыми полями. Для обсуждения и простоты здесь приведен пример программы:
#include <stdio.h>
struct Node
{
int a:16 __attribute__ ((packed));
int b:16 __attribute__ ((packed));
unsigned int c:27 __attribute__ ((packed));
unsigned int d:3 __attribute__ ((packed));
unsigned int e:2 __attribute__ ((packed));
};
int main (int argc, char *argv[])
{
Node n;
n.a = 12345;
n.b = -23456;
n.c = 0x7ffffff;
n.d = 0x7;
n.e = 0x3;
printf("3-bit field cast to int: %d\n",(int)n.d);
n.d++;
printf("3-bit field cast to int: %d\n",(int)n.d);
}
Программа намеренно вызывает переполнение 3-битного битового поля. Здесь (правильный) вывод при компиляции с использованием "g++ -O0":
3-битное поле, отличное от int: 7
3-битное поле, отличное от int: 0
Здесь вывод при компиляции с использованием "g++ -O2" (и -O3):
3-битное поле, отличное от int: 7
3-битное поле, переданное в int: 8
Проверка сборки последнего примера, я нашел это:
movl $7, %esi
movl $.LC1, %edi
xorl %eax, %eax
call printf
movl $8, %esi
movl $.LC1, %edi
xorl %eax, %eax
call printf
xorl %eax, %eax
addq $8, %rsp
Оптимизации только что вставили "8", предположив, что 7 + 1 = 8, когда на самом деле число переполняется и равно нулю.
К счастью, код, о котором я забочусь, не переполняется, насколько я знаю, но эта ситуация пугает меня - это известная ошибка, функция или это ожидаемое поведение? Когда я могу ожидать, что gcc будет прав по этому поводу?
Изменить (re: signed/unsigned):
Он обрабатывается как unsigned, потому что он объявлен как unsigned. Объявляя его как int, вы получаете результат (с O0):
3-битное поле, переданное в int: -1
3-битное поле, отличное от int: 0
В этом случае случается еще более смешная ситуация с -O2:
3-битное поле, отличное от int: 7
3-битное поле, переданное в int: 8
Я признаю, что атрибут - это опасная вещь; в этом случае это разница в настройках оптимизации, о которых я беспокоюсь.
Ответы
Ответ 1
Если вы хотите получить техническую информацию, то в тот момент, когда вы использовали __attribute__
(идентификатор, содержащий два последовательных символа подчеркивания), ваш код имел/имел undefined поведение.
Если вы получите такое же поведение с удаленными, это выглядит как ошибка компилятора. Тот факт, что 3-битное поле обрабатывается как 7
, означает, что он рассматривается как неподписанный, поэтому, когда вы переполняете его, он должен действовать как любой другой неподписанный и давать вам по модулю арифметику.
Также было бы законно рассматривать бит-поле как подписанное. В этом случае первым результатом будет -1
, -3
или -0
(который может быть напечатан как только 0
), а второй undefined (поскольку переполнение целого числа со знаком дает поведение undefined). Теоретически, другие значения могут быть возможны в рамках C89 или текущего стандарта С++, поскольку они не ограничивают представления целых чисел со знаком. В C99 или С++ 0x это могут быть только те три (C99 ограничены целыми знаками для одного дополнения, два дополнения или знака и С++ 0x основаны на C99 вместо C90).
К сожалению, я не уделял достаточно пристального внимания - так как он определен как unsigned
, его нужно рассматривать как unsigned
, оставляя мало места для маневра, чтобы выйти из его ошибки в компиляторе.