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, оставляя мало места для маневра, чтобы выйти из его ошибки в компиляторе.