Почему присвоение значения битовому полю не возвращает это же значение?

Я видел код ниже в этом сообщении 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++.

Ответы

Ответ 1

Битовые поля невероятно плохо определены стандартом. Учитывая этот код struct mystruct {int enabled:1;}; тогда мы не знаем:

  • Сколько места это занимает - если есть биты/байты заполнения и где они расположены в памяти.
  • Где бит находится в памяти. Не определено и также зависит от порядка байтов.
  • Следует ли рассматривать битовое поле int:n как подписанное или без знака.

Что касается последней части, C17 6.7.2.1/10 говорит:

Битовое поле интерпретируется как имеющее целочисленный тип со знаком или без знака, состоящий из указанного числа битов 125)

Ненормативная записка, поясняющая вышесказанное:

125) Как указано в 6.7.2 выше, если фактическим используемым спецификатором типа является int или typedef-name, определенное как int, то определяется реализацией, является ли битовое поле подписанным или беззнаковым.

В случае, если битовое поле должно рассматриваться как signed int и вы делаете бит размером 1, тогда нет места для данных, только для знакового бита. По этой причине ваша программа может давать странные результаты на некоторых компиляторах.

Хорошая практика:

  • Никогда не используйте битовые поля для каких-либо целей.
  • Избегайте использования типа int знаком для любой формы манипуляции битами.

Ответ 2

Я не могу понять, как это возможно, что мы что-то устанавливаем, и тогда это не появляется, как есть.

Вы спрашиваете, почему он компилирует и дает ошибку?

Да, в идеале это должно дать вам ошибку. И это так, если вы используете предупреждения вашего компилятора. В 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;
           ^

Причины, по которым это оставлено на усмотрение реализации или ошибки, могут иметь большее отношение к историческому использованию, где требование приведения означало бы разрушение старого кода. Авторы стандарта могут полагать, что предупреждений было достаточно, чтобы восполнить провал для заинтересованных сторон.

Чтобы добавить некоторый прескриптивизм, я повторю утверждение @Lundin: "Никогда не используйте битовые поля для каких-либо целей". Если у вас есть веские причины для получения низкоуровневых и конкретных сведений о разметке памяти, из-за которых вы могли бы подумать, что вам в первую очередь нужны битовые поля, другие связанные с вами требования почти наверняка столкнутся с их недостаточной спецификацией.

(TL; DR - если вы достаточно опытны, чтобы законно "нуждаться" в битовых полях, они недостаточно четко определены, чтобы обслуживать вас.)

Ответ 3

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

struct mystruct { int enabled:1; };

объявляет enable как 1-битное битовое поле. Поскольку он подписан, допустимыми значениями являются -1 и 0. Установка поля в 1 переполняет этот бит, возвращаясь к -1 (это неопределенное поведение)

По существу, при работе со битовым полем со знаком максимальное значение составляет 2^(bits - 1) - 1 что в данном случае равно 0.

Ответ 4

Вы могли бы думать об этом как о том, что в системе с 2 дополнениями самый левый бит является знаковым битом. Таким образом, любое целое число со знаком с самым левым установленным битом является отрицательным значением.

Если у вас есть 1-битное целое число со знаком, оно имеет только бит знака. Таким образом, присвоение 1 этому единственному биту может установить только знаковый бит. Таким образом, при чтении обратно значение интерпретируется как отрицательное, как и -1.

Значения, которые может содержать 1-битное целое число со -2^(n-1)= -2^(1-1)= -2^0= -1 и 2^n-1= 2^1-1=0

Ответ 5

В соответствии со стандартом 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; }; struct mystruct { BOOL enabled:1; };

Выход:

Выключен !! (при использовании enum BOOL: int)

Если enum BOOL: int сделан простым enum BOOL, то вывод будет таким, как указано выше:

Включено (при использовании enum BOOL)


Следовательно, можно сделать вывод, также как и несколько других ответов, что тип int недостаточно велик, чтобы хранить значение "1" только в одном битовом поле.

Ответ 6

Из [dcl.init]

Акцент мой

При инициализации битового поля значением, которое оно не может представлять, результирующее значение битового поля определяется реализацией.

Ваше битовое поле имеет ширину в один бит. int слишком большой, чтобы быть представленным. Следовательно, результат определяется реализацией. То есть реализация может принимать самый левый бит (знаковый бит, который равен нулю) вместо самого правого бита.

Ответ 7

В вашем понимании битовых полей нет ничего плохого, что я вижу. Я вижу, что вы сначала переопределили mystruct как struct mystruct {int enabled: 1; } and then as struct mystruct s; } а затем как 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");
}