#defined bitflags and enums - мирное сосуществование в "c" - PullRequest
1 голос
/ 04 октября 2009

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

Для экономии места я использую одно целое 32-битное поле в структуре (A->flag) для представления нескольких различных наборов логических свойств. Всего 20 различных битов равны #define d. Некоторые из них - это действительно флаги присутствия / отсутствия (STORAGE-INTERNAL или STORAGE-EXTERNAL). Другие имеют более двух значений (например, взаимоисключающий набор форматов: FORMAT-A, FORMAT-B, FORMAT-C). Я определил макросы для установки определенных битов (и одновременного отключения взаимоисключающих битов). Я также определил макросы для проверки, установлена ​​ли конкретная комбинация битов во флаге.

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

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

  2. (b) Стоит ли беспокоиться о разнице в размерах при хранении A->flag против #define d констант и перечислений?

  3. (c) Я делаю вещи излишне сложными, то есть я должен просто придерживаться использования #define d констант для передачи параметров как обычных int s? О чем еще мне беспокоиться во всем этом?

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

Ответы [ 6 ]

7 голосов
/ 04 октября 2009

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

Итак, это заботится о (а).

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

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

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

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

6 голосов
/ 04 октября 2009

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

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;
}

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

3 голосов
/ 04 октября 2009

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

Ваша проблема (б) не является проблемой; 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 как непрозрачный и предоставлять «функции» (или макросы) для получения или установки всех полей. Чем меньше людей могут ошибаться, тем меньше будет ошибок.

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

Хммм ... пока ничего особенного.

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

Примерно так:

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 безопасно, если используется точно, но ужасно при злоупотреблении. Одним из преимуществ макросов является то, что вы можете заменить их функцией, которая тщательно проверяет вещи при необходимости; это хорошо работает, если люди используют макросы последовательно.

2 голосов
/ 04 октября 2009

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

1 голос
/ 04 октября 2009

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

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

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

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

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

~ Русь

...