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-разрядный полевой бросок к интервалу: 7

3-разрядный полевой бросок к интервалу: 0

Вот вывод, когда скомпилировано с помощью "g ++-O2" (и-O3):

3-разрядный полевой бросок к интервалу: 7

3-разрядный полевой бросок к интервалу: 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 будет прав относительно этого?

Редактирование (ре: со знаком/неподписанный):

Это рассматривают как неподписанное, потому что это объявляется как неподписанное. При объявлении этого как интервала Вы получаете вывод (с O0):

3-разрядный полевой бросок к интервалу:-1

3-разрядный полевой бросок к интервалу: 0

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

3-разрядный полевой бросок к интервалу: 7

3-разрядный полевой бросок к интервалу: 8

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

10
задан Rooke 14 May 2010 в 19:56
поделиться

1 ответ

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

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

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

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

8
ответ дан 4 December 2019 в 02:25
поделиться