Сегодня я обнаружил сигнализирующее поведение при экспериментировании с битовыми полями. Ради обсуждения и простоты, вот пример программы:
#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
Я признаю, что атрибут является подозрительной вещью использовать; в этом случае вот в чем разница в настройках оптимизации, которыми я обеспокоен.
Если вы хотите быть технически подкованным, то как только вы использовали __attribute__
(идентификатор, содержащий два последовательных подчеркивания), ваш код имел/имеет неопределенное поведение.
Если вы получаете такое же поведение при удалении этих идентификаторов, то, на мой взгляд, это похоже на ошибку компилятора. Тот факт, что 3-битное поле обрабатывается как 7
, означает, что оно обрабатывается как беззнаковое, поэтому при переполнении оно должно работать как любое другое беззнаковое и давать арифметику по модулю.
Также было бы законно рассматривать битовое поле как знаковое. В этом случае первый результат будет -1
, -3
или -0
(который может быть выведен как просто 0
), а второй - неопределенным (поскольку переполнение знакового целого дает неопределенное поведение). Теоретически, другие значения могут быть возможны в C89 или текущем стандарте C++, поскольку они не ограничивают представления знаковых целых чисел. В C99 или C++0x это могут быть только эти три значения (C99 ограничивает знаковые целые числа дополнением единицы, дополнением двойки или знаком величины, а C++0x основан на C99, а не на C90).
Упс: Я не был достаточно внимателен - поскольку оно определено как unsigned
, оно должно рассматриваться как unsigned
, что оставляет мало возможностей для выхода из того, что это ошибка компилятора.