Представитель Шуры Люберецкого в ЖЖ (brat_luber) wrote,
Представитель Шуры Люберецкого в ЖЖ
brat_luber

Category:

Волшебные константы, часть 2

Мне так понравилось разнообразие мнений по поводу написанного leetspeak-ом слова Bootload (там, где я спер этот скриншот, срач был под полторы сотни комментов), что предлагаю обсудить вот этот кусок кода, 13 лет в практически неизменном виде существующий в довольно популярном опенсорсном проекте:

Хотя… Пожалуй, обсуждать, по какой причине господин Mathieu Lacage старательно расписал enum для разных значений SUBTYPE_CTL_*** и не стал так делать для SUBTYPE_MGT_*** и SUBTYPE_DATA_*** будет довольно скучно (ответ: потому что мудак), а было бы забавней обсудить, как и почему для того, чтобы два байта отослать — я не вру — вот эти два байта октета:

— надо городить аж вот такую фигню:

uint8_t m_ctrlType;     ///< control type
uint8_t m_ctrlSubtype;  ///< control subtype
uint8_t m_ctrlToDs;     ///< control to DS
uint8_t m_ctrlFromDs;   ///< control from DS
uint8_t m_ctrlMoreFrag; ///< control more fragments
uint8_t m_ctrlRetry;    ///< control retry
uint8_t m_ctrlMoreData; ///< control more data
uint8_t m_ctrlWep;      ///< control WEP
uint8_t m_ctrlOrder;    ///< control order

Вообще, я видел три подхода к проблеме заполнения всякого рода "предопределенных" байтовых и битовых структур на C и C++. Подход первый, расточительный, продемонстрирован только что - заводим по полю избыточного, но удобного размера на каждый элемент структуры, а затем пишем две функции Serialize и Deserialize, в которых занимаемся странным байтоебством примерно в таком духе (да, волшебных констант тут налепили много):

uint16_t val = 0;
val |= (m_ctrlType << 2) & (0x3 << 2);
val |= (m_ctrlSubtype << 4) & (0xf << 4);
val |= (m_ctrlToDs << 8) & (0x1 << 8);
val |= (m_ctrlFromDs << 9) & (0x1 << 9);
val |= (m_ctrlMoreFrag << 10) & (0x1 << 10);
val |= (m_ctrlRetry << 11) & (0x1 << 11);
val |= (m_ctrlMoreData << 13) & (0x1 << 13);
val |= (m_ctrlWep << 14) & (0x1 << 14);
val |= (m_ctrlOrder << 15) & (0x1 << 15);
return val;

Вариант второй - экономично-эмбеддерский, выглядит примерно так:

uint16_t frameControl = 0;
frameControl |= (CTL_TYPE_DATA << FRAME_CONTROL_CONTROL_TYPE_OFFSET) & FRAME_CONTROL_CONTROL_TYPE_MASK;
frameControl |= (CTL_SUBTYPE_DATA_QOS << FRAME_CONTROL_CONTROL_SUBTYPE_OFFSET) & FRAME_CONTROL_CONTROL_SUBTYPE_MASK;

ctrlSubtype = (frameControl & FRAME_CONTROL_CONTROL_SUBTYPE_MASK) >> FRAME_CONTROL_CONTROL_SUBTYPE_OFFSET;
/* and so on */

Отличие от предыдущего - мы работаем напрямую с этими двумя байтиками, и это довольно экономично, нам не надо работать с "тяжеловесным" по эмбеддерским меркам объектом с десятком-другим полей, а то и целой ссылкой на vtable (если обратите внимание - функции Serialize и Deserialize виртуальные). Можно обернуть это все в несколько более удобные макросы или inline-функции.

Но неужели для такой стандартной задачи человечество не придумало ничего лучше? Подобного рода херней люди занимаются вот уже несколько десятков лет, и в полувековой давности языке C есть прекрасная штука - битовые поля. В книжке 1984 года The Unix Programming Environment они описывались, как рекомендованный и правильный способ вот этого битоебства. По удобству это напоминает первый способ, с членами структуры, а по экономичности полностью аналогично второму:

struct {
    unsigned int protocolVersion : 2;
    unsigned int type : 2;
    unsigned int subtype : 4;
    unsigned int toDS : 1;
    unsigned int fromDS : 1;
    unsigned int moreFragments : 1;
    unsigned int retry : 1;
    unsigned int powerManagement : 1;
    unsigned int moreData : 1;
    unsigned int protectedFrame : 1;
    unsigned int order : 1;
} frameControl;

frameControl.type = CTL_TYPE_DATA;
frameControl.subtype = CTL_SUBTYPE_DATA_QOS;

ctrlSubtype = frameControl.subtype;
/* and so on */

Но... если на уровне "байтиков" структуры в C и C++ еще как-то предсказуемы (а проблемы с выравниванием обычно решаются с помощью #pragma pack(1)), то вот битовые поля оказываются настолько непредсказуемы, что в книжке Кернигана и Ритчи приведено осторожное предупреждение, а в любом современном учебнике настоятельно рекомендуется ими никогда не пользоваться!

Что-то мне подсказывает, что вовсе не этого хотели Керниган с Ритчи. А что вы порекомендуете?

Запись опубликована в блоге Шуры Люберецкого. Вы можете оставлять свои комментарии там или в ЖЖ.

Subscribe

  • Про кофе

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

  • А как это называется?

    А скажите, как называется то чувство, когда ты сначала хуесосишь чувака в твиторе за тупость, а потом видишь его юзерпик в корпоративном чате? И…

  • Электронный документооборот

    Пока тут Исавнин с Артамоновым кидают друг в друга какахами на хабре из-за дистанционного электронного голосования — реальные электронные…

  • Post a new comment

    Error

    default userpic

    Your IP address will be recorded 

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 3 comments