Битовые манипуляции и флаги

https://i.imgur.com/VU56Rwn.png

A: Когда man-страница для open говорит: Указанные флаги формируются с помощью or'ing следующих значений:

       O_RDONLY        open for reading only
       O_WRONLY        open for writing only
       ...

это означает, что мы должны использовать логическое или между флагами, например: O_RDONLY || O_WRONLY, чтобы указать желаемую комбинацию разрешений.

B: Для указания различных опций мы используем битовые флаги (а не символы или целые числа) для экономии места.

C: Выполнение операций с битовыми флагами выполняется быстро.

D: Битовые флаги, используемые в системных вызовах, определяются в библиотечных файлах.

E: Команда chmod использует константы битовых флагов, определенные в восьмеричном формате, поскольку существует восемь возможных состояний разрешений.

Я знаю, что битовые флаги не определены в файлах библиотек, если это хорошая системная библиотека. Обычно это константы или #define в заголовке, а не скомпилированный объект, если это то, на что ссылаются «файлы библиотеки». Однако я не понимаю, как они экономят место, разве битовые флаги не просто целые числа?


person heskin reaper    schedule 18.03.2018    source источник
comment
Пожалуйста, не публикуйте изображения кода, вывода или задания, по крайней мере, будьте любезны написать задание в своем вопросе, чтобы нам не приходилось обращаться к внешним ресурсам.   -  person Pablo    schedule 19.03.2018
comment
Флаги ORing с || явно неверны   -  person harold    schedule 19.03.2018
comment
@Пабло Извините, исправлено.   -  person heskin reaper    schedule 19.03.2018
comment
Нет, флаги должны быть побитовыми или красными, а не логически, как вы показываете. Таким образом, 1_. Такие флаги должны храниться как биты в одной переменной.   -  person Weather Vane    schedule 19.03.2018
comment
| и || разные вещи. Первый — это побитовое ИЛИ, а второй — логический оператор ИЛИ.   -  person Pablo    schedule 19.03.2018
comment
Вы не комбинируете флаги O_RDONLY и O_WRONLY для получения разрешений на чтение и запись. Вместо этого вы используете O_RDWR. Только один из этих трех ( O_RDONLY или O_WRONLY или O_RDWR). Любое из этих трех значений можно комбинировать с другими флагами, упомянутыми в документации (O_CREAT, O_DIRECT и т. д.).   -  person ddbug    schedule 19.03.2018
comment
B в данном случае не выполняется; флаги в fopen("foo", "r") занимают меньше места (2 байта), чем флаги в open("foo", O_RDONLY) (скорее всего, целое число с 4 байтами).   -  person ensc    schedule 19.03.2018


Ответы (2)


Во-первых, | и || — разные операторы.

| - это побитовый оператор ИЛИ, который выполняет ИЛИ для каждого бита, и вы получаете результат этого.

|| — это логический оператор ИЛИ, который возвращает истину, если верна левая или правая часть, и ложь в противном случае.

В случае битового флага вы должны использовать |, например O_RDONLY | O_WRONLY

B: Для указания различных опций мы используем битовые флаги (а не символы или целые числа) для экономии места.

Я думаю, что эта формулировка немного вводит в заблуждение, если честно. Преимущество битовых флагов в том, что вы можете упаковать в один int, в основном 32-битный, до 32 различных значений, которые имеют семантику ON/OFF. Таким образом, функция, которая принимает такие параметры, должна использовать только один int, чтобы получить несколько параметров от вызывающей стороны, потому что отдельные биты представляют такое свойство включения/выключения. Если бит равен 1, свойство включено, иначе выключено.

В конструкции, когда вы не используете битовые флаги, функция должна будет принимать отдельную переменную для каждой опции, и если у вас много опций, вам понадобится много переменных в объявлении функции. Таким образом, вы можете «сэкономить место», если вместо этого используете битовые флаги.

Учти это:

// lot's of other has_property_x cases

int somefunc(int has_property_a, int has_property_b, int has_property_c, ...)
{
    if(has_property_a)
        do_something_based_on_a();
    if(has_property_b)
        do_something_based_on_b();
    ....
}

void foo(void)
{
    somefunc(1, 1, 0, 1, 1);
}

Это не очень эффективно, это трудно читать, это головная боль для кода, в целом не очень хороший дизайн.

Однако, если вы используете битовые флаги, вы можете сохранить множество переменных в функциях:

// lot's of other HAS_PROPERTY_X cases

#define HAS_PROPERTY_A 1
#define HAS_PROPERTY_B 2
#define HAS_PROPERTY_C 4
...

int somefunc(int flags)
{
    if(flags & HAS_PROPERTY_A)
        do_something_based_on_a();
    if(flags & HAS_PROPERTY_B)
        do_something_based_on_b();
    ...
}

void foo(void)
{
    somefunc(HAS_PROPERTY_A | HAS_PROPERTY_B | HAS_PROPERTY_E);
}

намного компактнее и читабельнее.

person Pablo    schedule 18.03.2018

Краткий обзор того, что такое Bitflags

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

Побитовые операторы — это такие операторы, как | (побитовое ИЛИ), & (побитовое И), ^ (побитовое исключающее ИЛИ) и ~ (побитовое НЕ), которые выполняют побитовую логическую операцию, указанную оператором над двумя значениями. для создания нового значения. Побитовые операторы отличаются от логических операторов, таких как || (логическое ИЛИ), && (логическое И), которые используются с выражениями, результатом которых является логическое значение true (не ноль) или false (ноль).

Примером типичного определения, использующего директивы препроцессора C define для создания побитовых флагов, может быть:

#define  ITM_FLAG_EXAMPLE1  0x00000001L   // an example bitwise flag
#define  ITM_FLAG_EXAMPLE2  0x00000002L   // another example flag
#define  ITM_FLAG_JUMP01    0x00040000L   // another example
#define  ITM_FLAG_JUMP02    0x00080000L   // another example

Кроме того, современные компиляторы C также позволяют использовать типы enum.

typedef enum { ITEM_FLAG_1 = 1, ITEM_FLAG_2 = 2, ITEM_FLAG_3 = 4 } ItemType;

ItemType AnItem = ITEM_FLAG_1;    // defining a variable of the type
ItemType AnItem2 = ITEM_FLAG_1 | ITEM_FLAG_2;  // defining a second variable

or

enum { ITEM_FLAG_1 = 1, ITEM_FLAG_2 = 2, ITEM_FLAG_3 = 4 } ItemType;

enum ItemType AnItem = ITEM_FLAG_1;    // defining a variable of the type
enum ItemType AnItem2 = ITEM_FLAG_1 | ITEM_FLAG_2;  // defining a second variable

И несколько примеров того, как их можно использовать:

unsigned long ulExample = ITM_FLAG_EXAMPLE2;   // ulExample contains 0x00000002L
unsigned long ulExamplex = ITM_FLAG_EXAMPLE1 | ITM_FLAG_EXAMPLE2;  // ulExamplex contains 0x00000003L
unsigned long ulExampley = ulExamplex & ITM_FLAG_EXAMPLE2;  // ulExampley contains 0x00000002L

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

Некоторые рекомендации по использованию битовых флагов

При использовании битовых флагов может быть несколько ошибок и несколько вещей, на которые следует обратить внимание.

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

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

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

К опубликованным вопросам

Когда предоставляется библиотека, обычно имеется один или несколько включаемых файлов, которые сопровождают файл библиотеки, чтобы предоставить ряд необходимых элементов.

  • прототипы функций для функций, предоставляемых библиотекой
  • объявления типов переменных и определения для типов, используемых библиотекой
  • специальные константы для операндов и флаги, управляющие поведением библиотечных функций

Битовые флаги — проверенный временем способ предоставления опций для функционального интерфейса. Битовые флаги имеют несколько замечательных свойств, которые делают их привлекательными для программиста на C.

  • компактное представление, которое легко передать через интерфейс
  • естественно подходит для операций булевой алгебры и манипулирования множествами
  • естественное соответствие побитовым операторам C для выполнения этих операций

Битовые флаги экономят место, потому что имя флага может быть длинным и описательным, но компилируется в одно значение без знака, такое как unsigned short или unsigned long или unsigned char.

Другое приятное свойство использования битовых флагов заключается в том, что при использовании побитовых операций над константами в выражении большинство современных компиляторов оценивают побитовую операцию как часть компиляции выражения. Таким образом, современный компилятор будет принимать несколько побитовых операторов в выражении, таком как O_RDONLY | O_WRONLY, и выполнять побитовое ИЛИ при компиляции исходного кода и заменять выражение значением вычисляемого выражения.

В большинстве компьютерных архитектур побитовые операторы выполняются с использованием регистров, в которые загружаются данные, а затем выполняется побитовая операция. Для 32-битной архитектуры использование 32-битной переменной для хранения набора битов естественным образом вписывается в регистры ЦП, так же как в 64-битной архитектуре использование 32- или 64-битной переменной для хранения набора битов естественным образом вписывается в регистры. Это естественное соответствие позволяет выполнять несколько побитовых операций над одними и теми же переменными без необходимости выполнять выборку из кэша ЦП или основной памяти.

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

Компактное представление битовых флагов можно легко увидеть, используя unsigned long для передачи 32 различных флагов или unsigned long long для передачи 64 различных флагов в функцию. Массив unsigned char можно использовать для передачи гораздо большего количества флагов, используя метод смещения массива и битового флага вместе с набором макросов процессора C или набором функций для управления массивом.

Некоторые примеры того, что возможно

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

#define FLAG_1  0x00000001L     // a required flag if FLAG_2 is specified
#define FLAG_2  0x00001000L     // must not be specified with FLAG_3
#define FLAG_3  0x00002000L     // must not be specified with FLAG_2

int func (unsigned long ulFlags)
{
    // check if both FLAG_2 and FLAG_3 are specified. if so error
    // we do a bitwise And to isolate specific bits and then compare that
    // result with the bitwise Or of the bits for equality. this approach
    // makes sure that a check for both bits is turned on.
    if (ulFlags & (FLAG_2 | FLAG_3) == (FLAG_2 | FLAG_3)) return -1;

    // check to see if either FLAG_1 or FLAG_3 is set we can just do a
    // bitwise And against the two flags and if either one or both are set
    // then the result is non-zero.
    if (ulFlags & (FLAG_1 | FLAG_3)) {
        // do stuff if either or both FLAG_1 and/or FLAG_3 are set
    }

    // check that required option FLAG_1 is specified if FLAG_2 is specified.
    // we are using zero is boolean false and non-zero is boolean true in
    // the following. the ! is the logical Not operator so if FLAG_1 is
    // not set in ulFlags then the expression (ulFlags & FLAG_1) evaluates
    // to zero, False, and the Not operator inverts the False to True or
    // if FLAG_1 is set then (ulFlags & FLAG_1) evaluates to non-zero, True,
    // and the Not operator inverts the True to False. Both sides of the
    // logical And, &&, must evaluate True in order to trigger the return.
    if ((ulFlags & FLAG_2) && ! (ulFlags & FLAG_1)) return -2;
    // other stuff
}

Например, см. краткий обзор Использование select() для неблокирующих сокетов. стандартного интерфейса socket() использования битовых флагов и функции select() для примеров использования абстракции манипулирования наборами, аналогичной тому, что можно сделать с битовыми флагами. Функции socket позволяют задавать различные характеристики, такие как неблокировка, за счет использования битовых флагов с функцией fcntl(), а функция select() имеет связанный набор функций/макросов (FD_SET(), FD_ZERO() и т. д.), который обеспечивает абстракцию для указывая, какие дескрипторы сокета должны отслеживаться в файле select(). Я не имею в виду, что наборы сокетов select() являются битовыми картами, хотя я полагаю, что в оригинальной UNIX они были такими. Однако абстрактный дизайн select() и связанных с ним утилит предоставляет своего рода наборы, которые могут быть реализованы с помощью битовых флагов.

Вычисление переменной, содержащей битовые флаги, также может быть быстрее, проще, эффективнее и читабельнее. Например, в функции, вызываемой с некоторыми из определенных флагов:

#define ITEM_FLG_01  0x0001
#define ITEM_FLG_02  0x0002
#define ITEM_FLG_03  0x0101
#define ITEM_FLG_04  0x0108
#define ITEM_FLG_05  0x0200

#define ITEM_FLG_SPL1 (ITEM_FLG_01 | ITEM_FLG_02)

может быть оператор switch(), такой как:

switch (bitwiseflags & ITEM_FLG_SPL1) {
    case ITEM_FLG_01 | ITEM_FLG_02:
        // do things if both ITEM_FLG_01 and ITEM_FLG_02 are both set
        break;
    case ITEM_FLG_01:
        // do things if ITEM_FLG_01  is set
        break;
    case ITEM_FLG_02:
        // do things if ITEM_FLG_02 is set
        break;
    default:
        // none of the flags we are looking for are set so error
        return -1;
}

И вы можете сделать несколько коротких и простых выражений, таких как следующие, используя те же определения, что и выше.

// test if bitwiseflags has bit ITEM_FLG_5 set and if so then call function
// doFunc().
(bitwiseflags & ITEM_FLG_5) == ITEM_FLG_5 && doFunc();

Дополнение: метод для очень больших наборов битовых флагов

См. этот ответ, Создание переменных битовых флагов с большим количеством флагов или как создавать большие числа битовой ширины для подхода для больших наборы битовых флагов больше, чем может поместиться в 32-битной или 64-битной переменной.

person Richard Chambers    schedule 18.03.2018
comment
Я не думаю, что это switch утверждение работает так, см. ideone.com/Gg5Kjs, или я что-то упустил ? - person Pablo; 19.03.2018
comment
@Pablo зависит от того, являются ли эти битовые флаги значениями #define или чем-то вроде const int O_RDONLY = 0x001;. Если #define, то на этапе препроцессора текст O_RDONLY будет переведен в представленное значение константы, и компилятор увидит только такие константы, как case 0x0001 | 0x0002:. Но если используется const int или подобное, я вижу ошибку компилятора. - person Richard Chambers; 19.03.2018
comment
Я не понимаю, что ты пытаешься сказать. С const int A = 1 я получаю error: case label does not reduce to an integer constant. Когда я делаю #define A или помещаю их в enum, он компилируется. Но дело default выполняется. В вашем коде case O_RDONLY | O_WRONLY будет выполняться тогда и только тогда, когда установлены эти два бита, а другой бит не установлен. Если кроме них установлен любой другой бит, то блок case выполняться не будет. - person Pablo; 19.03.2018
comment
@ Пабло, я поверю тебе на слово насчет enum, так как я этого не пробовал. Вы правы во всем остальном. Какой у Вас вопрос? - person Richard Chambers; 19.03.2018
comment
O_RDONLY может быть равно нулю (в большинстве систем равно), поэтому переключатель не будет компилироваться. - person wildplasser; 19.03.2018
comment
Ноль @wildplasser является вполне допустимым значением для case и отлично скомпилируется. Я только что вытащил пример из поста OP для switch() просто для использования в качестве заполнителей, не имея представления о том, какими будут определения. Это пример того, что возможно, а не рецепт того, что нужно делать. - person Richard Chambers; 19.03.2018
comment
@wildplasser это меняет твою дикую грудь? - person Richard Chambers; 19.03.2018
comment
Вы не видите проблемы. 0 | n == n для каждого значения n. Итак, учитывая, что O_RDONLY равно нулю: O_RDONLY | O_WRONLY==O_WRONLY, это приводит к дублированию случаев в вашем коммутаторе. - person wildplasser; 19.03.2018
comment
@wildplasser Я вижу проблему с тем, что O_RDONLY определяется с нулевым значением и используется в предыдущей версии переключателя. Чего вы не понимаете, так это того, что это был общий пример, поэтому я перешел к конкретным определенным константам. - person Richard Chambers; 19.03.2018
comment
@RichardChambers вы изменили код, но проблема, которую я вижу, осталась. Я не знаю, это ли вы сделали отступ, но в case GEORGE | BROWNIE: комментарий // do things if both GEORGE and BROWNIE are both set должен быть // do things if and only if GEORGE and BROWNIE are both set and nothing else. Этот case не будет обрабатывать другие случаи, когда установлены другие биты. - person Pablo; 19.03.2018
comment
@Пабло, хорошо, я сдаюсь. Это пример метода, который я использую во встраиваемых программах, и я решил включить его. Я не ожидал, что меня будут бить молотком и придираться. Это было сделано быстро, чтобы показать пример, где наборы, реализованные с набором битовых масок для представления различных состояний, могут быть полезны. - person Richard Chambers; 19.03.2018
comment
У меня проблема с семантикой комментариев. Например, case GEORGE: вы говорите делать что-то, если установлен GEORGE. Я знаю, что вы имеете в виду, но технически это неверно; этот случай выполняется, только если bitwiseflags == GEORGE, а не если установлен бит, представляющий GEORGE. Для тех, у кого все еще есть непонимание битовых флагов, это может сбить с толку. - person Pablo; 19.03.2018
comment
@Pablo, сделай мне одолжение и просмотри изменения, которые я сделал. Оригинал был сделан в спешке, так как за публикацию быстро опускали голоса до -3, а близкие голоса уже начали поступать. Я не был уверен, сохранится ли публикация или ОП просто удалит ее из-за давления сообщества. И так как я подумал, что это хороший вопрос, я хотел придумать что-нибудь для начала. - person Richard Chambers; 20.03.2018
comment
@wildplasser сделайте мне одолжение и просмотрите внесенные мной изменения. Оригинал был сделан в спешке, так как за публикацию быстро опускали голоса до -3, а близкие голоса уже начали поступать. Я не был уверен, сохранится ли публикация или ОП просто удалит ее из-за давления сообщества. И так как я подумал, что это хороший вопрос, я хотел придумать что-нибудь для начала. - person Richard Chambers; 20.03.2018
comment
Нет, не буду. Ваш первоначальный ответ был структурно неправильным. [как в: правильный ответ на вопрос вонг] - person wildplasser; 21.03.2018