#определенные битовые флаги и перечисления - мирное сосуществование в “c”

StackOverflow https://stackoverflow.com/questions/1515086

  •  19-09-2019
  •  | 
  •  

Вопрос

Я только что открыл для себя всю прелесть bitflags.У меня есть несколько вопросов, связанных с "лучшими практиками" относительно использования битовых флагов в C.Я узнал все из различных примеров, которые нашел в Интернете, но у меня все еще есть вопросы.

Чтобы сэкономить место, я использую одно 32-битное целое поле в структуре (A->flag) для представления нескольких различных наборов логических свойств.Всего 20 различных битов являются #defined.Некоторые из них действительно являются флагами присутствия / отсутствия (ХРАНИЛИЩЕ-ВНУТРЕННЕЕ противХРАНИЛИЩЕ-ВНЕШНЕЕ).Другие имеют более двух значений (например,взаимоисключающий набор форматов:ФОРМАТ-A, ФОРМАТ-B, ФОРМАТ-C).Я определил макросы для установки определенных битов (и одновременного отключения взаимоисключающих битов).Я также определил макросы для проверки того, установлена ли во флаге определенная комбинация битов.

Однако то, что упускается при описанном выше подходе, - это конкретная группировка флагов, которая лучше всего улавливается перечислениями.Для написания функций я хотел бы использовать перечисления (например, STORAGE-TYPE и FORMAT-TYPE), чтобы определения функций выглядели красиво.Я ожидаю использовать перечисления только для передачи параметров и макросы #defined для установки и тестирования флагов.

  1. (a) Как мне определить флаг (A->flag) как 32-разрядное целое число переносимым способом (для 32-разрядных / 64-разрядных платформ)?

  2. (b) Должен ли я беспокоиться о потенциальных различиях в размерах того, как A->flag против. #defined константы противсохраняются перечисления?

  3. (c) Не усложняю ли я ситуацию излишне, то есть должен ли я просто придерживаться использования #defined константы для передачи параметров как обычные intс?О чем еще мне следует беспокоиться во всем этом?

Я приношу извинения за плохо сформулированный вопрос.Это отражает мое незнание потенциальных проблем.

Это было полезно?

Решение

Как уже говорили другие, ваша проблема (а) решаема с помощью <stdint.h> и либо uint32_t или uint_least32_t (если вы хотите беспокоиться о мэйнфреймах Burroughs, которые содержат 36-битные слова).Обратите внимание, что MSVC не поддерживает C99, но @DigitalRoss показывает, где вы можете получить подходящий заголовок для использования с MSVC.

Ваша проблема (b) - это не проблема;C безопасно введет convert для вас, если это необходимо, но, вероятно, в этом даже нет необходимости.

Область, вызывающая наибольшую озабоченность, - это (c) и, в частности, подполе формата.Там допустимы 3 значения.Вы можете справиться с этим, выделив 3 бита и потребовав, чтобы 3-разрядное поле было одним из значений 1, 2 или 4 (любое другое значение недопустимо из-за слишком большого количества или слишком малого количества установленных битов).Или вы могли бы выделить 2-разрядное число и указать, что либо 0, либо 3 (или, если вы действительно хотите, одно из 1 или 2) недопустимо.Первый подход использует еще один бит (в настоящее время это не проблема, поскольку вы используете только 20 из 32 бит), но является чисто битовым подходом.

При написании вызовов функций особых проблем с написанием не возникает:

some_function(FORMAT_A | STORAGE_INTERNAL, ...);

Это будет работать независимо от того, является ли FORMAT_A #define или enum (при условии, что вы правильно укажете значение enum).Вызываемый код должен проверить, не нарушилась ли концентрация у вызывающего абонента, и написать:

some_function(FORMAT_A | FORMAT_B, ...);

Но это внутренняя проверка, о которой должен беспокоиться модуль, а не проверка, о которой должны беспокоиться пользователи модуля.

Если люди собираются переключать биты в flags как правило, макросы для настройки и отмены настройки поля формата могут оказаться полезными.Кто-то может возразить, что любые чисто логические поля едва ли нуждаются в этом (и я бы посочувствовал).Возможно, было бы лучше всего лечить flags член как непрозрачный и предоставляет "функции" (или макросы) для получения или установки всех полей.Чем меньше людей могут ошибаться, тем меньше будет ошибок.

Подумайте, подходит ли вам использование битовых полей.Мой опыт показывает, что они приводят к созданию большого кода и не обязательно очень эффективного;ИММВ.

Хммм ... Пока здесь нет ничего определенного.

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

Вот так, возможно:

enum { ..., FORMAT_A = 0x0010, FORMAT_B = 0x0020, FORMAT_C = 0x0040, ... };
enum { FORMAT_MASK = FORMAT_A | FORMAT_B | FORMAT_C };

#define SET_FORMAT(flag, newval)    (((flag) & ~FORMAT_MASK) | (newval))
#define GET_FORMAT(flag)            ((flag) & FORMAT_MASK)

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

Другие советы

Существует заголовок C99, который был предназначен для решения именно этой проблемы (a), но по какой-то причине Microsoft не реализует его.К счастью, вы можете получить <stdint.h> для Microsoft Windows здесь.На любой другой платформе это уже будет.32-разрядными типами int являются uint32_t и int32_t.Они также выпускаются в 8, 16 и 64-разрядных версиях.

Итак, это решает проблему (a).

(b) и (c) - это в некотором роде один и тот же вопрос.Мы действительно делаем предположения всякий раз, когда что-то разрабатываем.Вы предполагаете, что C будет доступен.Вы предполагаете , что <stdint.h> можно где-нибудь найти.Вы всегда могли бы предположить, что int было не менее 16 бит, и теперь предположение > = 32 бит является достаточно разумным.

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

Вам не следует беспокоиться о производительности на операционном уровне до тех пор, пока (а) вы не заметите отставания в производительности и (б) вы не профилируете свою программу.Вам нужно выполнять свою работу, не увязая в заботах об отдельных операциях.:-)

О, я должен добавить, что вам особенно не нужно беспокоиться о низком уровне производительности когда вы пишете программу на C в первую очередь.C - это максимально приближенный к металлу язык, работающий максимально быстро.Мы обычно пишем материал на php, python, ruby или lisp, потому что нам нужен мощный язык, а процессоры в наши дни настолько быстры, что нам может сойти с рук целый интерпретатор, не говоря уже о неидеальном выборе операций с битовыми значениями длины слова.:-)

Вы можете использовать битовые поля и позволить компилятору изменять размер битов.Например:

struct PropertySet {
  unsigned internal_storage : 1;
  unsigned format : 4;
};

int main(void) {
  struct PropertySet x;
  struct PropertySet y[10]; /* array of structures containing bit-fields */
  if (x.internal_storage) x.format |= 2;
  if (y[2].internal_storage) y[2].format |= 2;
  return 0;
}

Отредактировано для добавления массива структур

Что касается вопроса а, если вы используете C99 (вы, вероятно, используете его), вы можете использовать uint32_t предопределенного типа (или, если он не предопределен, его можно найти в stdint.h заголовочный файл).

Относительно (c):если ваши перечисления определены правильно, вы сможете передавать их в качестве аргументов без проблем.Несколько вещей, которые следует учитывать:

  1. хранилище перечислений часто зависит от компилятора, поэтому в зависимости от того, какую разработку вы выполняете (вы не упоминаете, является ли это Windows vs.Linux противвстроенный противвстроенный Linux :)) возможно, вы захотите посетить параметры компилятора для enum хранилище, чтобы убедиться, что там нет проблем.Я в целом согласен с приведенным выше консенсусом о том, что компилятор должен соответствующим образом выполнять ваши перечисления, но об этом нужно знать.
  2. в случае, если вы выполняете встроенную работу, многие программы статической проверки качества , такие как PC Lint , будут "лаять", если вы начнете слишком усердствовать с перечислениями, #defines и битовыми полями.Если вы занимаетесь разработкой, которая должна проходить через любые критерии качества, это может быть чем-то, о чем следует помнить.Фактически, некоторые автомобильные стандарты (такие как MISRA-C) становятся откровенно раздражительными, если вы пытаетесь получить тригонометрию с битовыми полями.
  3. "Я только что открыл для себя всю прелесть bitflags". Я согласен с вами - я нахожу их очень полезными.

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

Я также читал, что перечисления хранятся в виде небольших целых чисел, что, насколько я понимаю, не является проблемой для логических тестов, поскольку они будут переформулироваться, начиная с самых правых битов.Но можно ли использовать перечисления для хранения больших целых чисел (1 << 21)??

еще раз спасибо вам всем.Я уже узнал больше, чем два дня назад!!

~Расс

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top