GCC, -O2 и битовые поля - это ошибка или фича? - PullRequest
10 голосов
/ 14 мая 2010

Сегодня я обнаружил тревожное поведение при экспериментировании с битовыми полями. Ради обсуждения и простоты, вот пример программы:

#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 будет прав по этому поводу?

Редактировать (повторно: подписано / не подписано):

Он рассматривается как неподписанный, потому что он объявлен как неподписанный. Объявив его как int, вы получите вывод (с O0):

3-битное поле приведено к int: -1

3-битное поле приведено к int: 0

Еще более забавная вещь случается с -O2 в этом случае:

3-битное поле приведено к int: 7

3-битное поле приведено к int: 8

Я признаю, что атрибут - это подозрительная вещь; в этом случае меня беспокоит разница в настройках оптимизации.

1 Ответ

8 голосов
/ 14 мая 2010

Если вы хотите получить техническую информацию, в ту минуту, когда вы использовали __attribute__ (идентификатор, содержащий два последовательных подчеркивания), ваш код имел / имел неопределенное поведение.

Если вы получаете то же самое поведение с удаленными, это выглядит для меня как ошибка компилятора. Тот факт, что 3-битное поле обрабатывается как 7, означает, что оно обрабатывается как беззнаковое, поэтому при переполнении оно должно поступать так же, как любое другое беззнаковое, и давать вам арифметику по модулю.

Было бы также допустимо для него рассматривать битовое поле как подписанное. В этом случае первый результат будет -1, -3 или -0 (который может печататься как просто 0), а второй не определен (так как переполнение целого числа со знаком дает неопределенное поведение). Теоретически, другие значения могут быть возможны в соответствии с C89 или текущим стандартом C ++, поскольку они не ограничивают представления целых чисел со знаком. В C99 или C ++ 0x это могут быть только эти три (C99 ограничивает целые числа со знаком одним дополнением, дополнением до двух или величиной знака, а C ++ 0x основан на C99 вместо C90).

Упс: я не уделил достаточно пристального внимания - поскольку он определен как unsigned, его нужно трактовать как unsigned, оставляя немного места для маневра, чтобы избавиться от ошибки компилятора.

...