Правила программирования на Си и Си++

       

Избегайте битовых масок; используйте битовые поля


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

struct fred

{

   int status;

   // ...§

};

#define CONDITION_A    0x01

#define CONDITION_B    0x02

#define CONDITION_C    0x03

#define SET_CONDITION_A(p)    ((p)-status |= CONDITION_A)

#define SET_CONDITION_B(p)    ((p)-status |= CONDITION_B)

#define SET_CONDITION_C(p)    ((p)-status |= CONDITION_C)

#define CLEAR_CONDITION_A(p)  ((p)-status = ~CONDITION_A)



#define CLEAR_CONDITION_B(p)  ((p)-status = ~CONDITION_B)

#define CLEAR_CONDITION_C(p)  ((p)-status = ~CONDITION_C)

#define IN_CONDITION_A(p)     ((p)-status CONDITION_A)

#define IN_CONDITION_B(p)     ((p)-status CONDITION_B)

#define IN_CONDITION_C(p)     ((p)-status CONDITION_C)

#define POSSIBILITIES(x)      ((x) 0x0030)

#define POSSIBILITY_A          0x0000

#define POSSIBILITY_B          0x0010

#define POSSIBILITY_C          0x0020

#define POSSIBILITY_D          0x0030

Это означает необходимость в дополнение к полю из структуры данных сопровождать 17 макросов, которые к тому же будут, вероятно, спрятаны где-то в заголовочном файле, а не в том, где они используются. Ситуация еще более ухудшится, если вы не включите эти макросы и организуете проверки прямо в программе. Что-нибудь типа:

if ( struct.status = ~CONDITION_A )

 // ...

по меньшей мере, с трудом читается. Еще хуже нечто, подобное следующему:

struct.status = POSSIBILITY_A;

if ( POSSIBILITIES(struct.status) == POSSIBILITY_A )

// ...

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

enum { possibility_a, possibility_b, possibility_b, possibility_d };

struct fred

{

unsigned in_condition_a : 1;

unsigned in_condition_b : 1;

unsigned in_condition_c : 1;

unsigned possibilities : 2;

};

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

struct fred flintstone;

flintstone.in_condition_a = 1;

if ( flintstone.in_condition_a )

// ...

flintstone.possibilities = possibility_b;

if ( flintstone.possibilities == possibility_a )

// ...

Единственным очевидным исключением из этого правила является взаимодействие с архитектурами со страничной организацией памяти; битовые поля не гарантируют какого-то упорядочивания в типе int, из которого выделяются биты.



Содержание раздела