Почему присвоение значения битовому полю не возвращает это же значение?
Я видел код ниже в этом сообщении Quora:
#include <stdio.h>
struct mystruct { int enabled:1; };
int main()
{
struct mystruct s;
s.enabled = 1;
if(s.enabled == 1)
printf("Is enabled\n"); // --> we think this to be printed
else
printf("Is disabled !!\n");
}
В обоих C и C++ вывод кода является неожиданным,
Выключен!!
Хотя в этом посте дается объяснение, связанное со "знаковым битом", я не могу понять, как это возможно, что мы что-то устанавливаем, и тогда это не отражает так, как есть.
Может кто-нибудь дать более подробное объяснение?
Примечание. Оба тега c & C++ являются обязательными, поскольку их стандарты немного отличаются для описания битовых полей. Смотрите ответы по спецификации C и C++.
6 ответов
Битовые поля невероятно плохо определены стандартом. Учитывая этот код struct mystruct {int enabled:1;};
тогда мы не знаем:
- Сколько места это занимает - если есть биты / байты заполнения и где они расположены в памяти.
- Где бит находится в памяти. Не определено и также зависит от порядка байтов.
- Будь
int:n
битовое поле считается подписанным или неподписанным.
Что касается последней части, C17 6.7.2.1/10 говорит:
Битовое поле интерпретируется как имеющее целочисленный тип со знаком или без знака, состоящий из указанного числа битов 125)
Ненормативная записка, поясняющая вышесказанное:
125) Как указано в 6.7.2 выше, если фактический используемый спецификатор типа
int
или typedef-имя, определенное какint
затем определяется реализацией, является ли битовое поле подписанным или неподписанным.
В случае, если битовое поле следует рассматривать как signed int
и вы делаете немного размера 1
, тогда нет места для данных, только для знакового бита. Это причина, почему ваша программа может давать странные результаты на некоторых компиляторах.
Хорошая практика:
- Никогда не используйте битовые поля для каких-либо целей.
- Избегайте использования подписанных
int
введите для любой формы битовых манипуляций.
Я не могу понять, как это возможно, что мы что-то устанавливаем, и тогда это не появляется, как есть.
Вы спрашиваете, почему он компилирует и дает ошибку?
Да, в идеале это должно дать вам ошибку. И это так, если вы используете предупреждения вашего компилятора. В GCC, с -Werror -Wall -pedantic
:
main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1'
changes value from '1' to '-1' [-Werror=overflow]
s.enabled = 1;
^
Причины, по которым это оставлено на усмотрение реализации или ошибки, могут быть связаны с историческим использованием, где требование приведения означало бы разрушение старого кода. Авторы стандарта могут полагать, что предупреждений было достаточно, чтобы восполнить провал для заинтересованных сторон.
Чтобы добавить некоторый прескриптивизм, я повторю высказывание @ Лундина: "Никогда не используйте битовые поля для каких-либо целей". Если у вас есть веские причины для получения низкоуровневых и точных сведений о разметке памяти, из-за которых вы могли бы подумать, что вам в первую очередь нужны битовые поля, другие связанные с вами требования почти наверняка столкнутся с их недостаточной спецификацией.
(TL; DR - если вы достаточно опытны, чтобы законно "нуждаться" в битовых полях, они недостаточно четко определены, чтобы обслуживать вас.)
Это поведение, определяемое реализацией. Я делаю предположение, что на машинах, на которых вы работаете, используются целые числа с двойным комплиментом и обрабатываются int
в этом случае в виде целого числа со знаком, чтобы объяснить, почему вы не вводите, если true, часть инструкции if.
struct mystruct { int enabled:1; };
объявляет enable
как 1-битное битовое поле. Поскольку он подписан, действительными значениями являются -1
а также 0
, Установка поля в 1
переполняет этот бит, возвращаясь к -1
(это неопределенное поведение)
По существу, при работе со битовым полем со знаком максимальное значение 2^(bits - 1) - 1
который 0
в этом случае.
Вы можете думать об этом как о том, что в системе дополнения 2 самый левый бит является знаковым битом. Таким образом, любое целое число со знаком с самым левым установленным битом является отрицательным значением.
Если у вас есть 1-битное целое число со знаком, оно имеет только бит знака. Так присваивая 1
к этому единственному биту можно установить только знаковый бит. Таким образом, при обратном чтении значение интерпретируется как отрицательное и равно -1.
Значения, которые может содержать 1-битное целое число: -2^(n-1)= -2^(1-1)= -2^0= -1
а также 2^n-1= 2^1-1=0
В соответствии со стандартом C++ n4713 предоставляется очень похожий фрагмент кода. Используемый тип BOOL
(пользовательский), но это может относиться к любому типу.
12.2.4
4 Если значение true или false хранится в битовом поле типа
bool
любого размера (включая одноразрядное битовое поле), оригиналbool
Значение и значение битового поля должны сравниваться равными. Если значение перечислителя хранится в битовом поле с тем же типом перечисления, а число битов в битовом поле достаточно велико, чтобы содержать все значения этого типа перечисления (10.2), исходное значение перечислителя и Значение битового поля должно сравниваться равным. [ Пример:enum BOOL { FALSE=0, TRUE=1 }; struct A { BOOL b:1; }; A a; void f() { a.b = TRUE; if (a.b == TRUE) // yields true { /* ... */ } }
- конец примера]
На первый взгляд жирная часть открыта для интерпретации. Тем не менее, правильное намерение становится ясным, когда enum BOOL
происходит от int
,
enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
struct mystruct s;
s.enabled = TRUE;
if(s.enabled == TRUE)
printf("Is enabled\n"); // --> we think this to be printed
else
printf("Is disabled !!\n");
}
С приведенным выше кодом это дает предупреждение без -Wall -pedantic
:
предупреждение: 'mystruct::enabled' слишком мало, чтобы содержать все значения 'enum BOOL'
struct mystruct { BOOL enabled:1; };
Выход:
Выключен!! (когда используешь
enum BOOL : int
)
Если enum BOOL : int
сделано просто enum BOOL
, тогда вывод будет таким, как указано выше в стандартной странице:
Включен (при использовании
enum BOOL
)
Следовательно, можно сделать вывод, также как и несколько других ответов, что int
Тип недостаточно велик, чтобы хранить значение "1" в одном битовом поле.
В вашем понимании битовых полей нет ничего плохого, что я вижу. Я вижу, что вы сначала переопределили mystruct как struct mystruct {int enabled: 1; } а затем как struct mystruct s;, То, что вы должны были закодировать, было:
#include <stdio.h>
struct mystruct { int enabled:1; };
int main()
{
mystruct s; <-- Get rid of "struct" type declaration
s.enabled = 1;
if(s.enabled == 1)
printf("Is enabled\n"); // --> we think this to be printed
else
printf("Is disabled !!\n");
}