FastNetMon

Monday 8 February 2016

Проблемы при использовании union и bit fields в C++

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

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

Вот пример проблемного кода, который выглядит на первый взгляд нормально и корректно: https://gist.github.com/pavel-odintsov/71538a198a0ba53ea156

Для удобства привожу основную структуру данных здесь:
typedef union __attribute__((__packed__)) {
    uint16_t reserved_flag : 1, dont_fragment_flag : 1, more_fragments_flag : 1, fragment_offset : 13;
    uint16_t fragmentation_details_as_integer;
} fragmentation_details_t;

Как можно видеть, идея была в том, чтобы иметь доступ к блоку из 4х битовых полей как к одному 16 битному целому (для удобства операций и оптимизации).

Но на деле этот код работает вовсе не так. А работать он будет так, что все 5 переменных будут разделять общую память с одинаковым смещением! То есть, установив одно из однобитовых битовых полей мы сразу же установим все прочие - так как они использую один и тот же адрес.

Как выглядит верное решение данной задачи?

Выглядит оно так - нужно внести дополнительную структур:
typedef union __attribute__((__packed__)) {
     struct { uint16_t fragment_offset : 13, more_fragments_flag : 1, dont_fragment_flag : 1, reserved_flag : 1;
    } fragmentation_details_pretty;

    uint16_t fragmentation_details_as_integer;
} fragmentation_details_t;

Такой подход не так красив, как задуманный ранее, потому что С и С++ не позволяют делать анонимные структуры и добавляется отдельный уровень вложенности. Но вот задачу данный подход решает на отлично! 

Update: как подсказал Andrew Stromnov, можно сделать намного круче - использовать GCC расширение и сделать анонимную структуру:

typedef union __attribute__((__packed__)) {
  struct {
    uint16_t fragment_offset : 13, more_fragments_flag : 1, dont_fragment_flag : 1, reserved_flag : 1; };
    uint16_t fragmentation_details_as_integer
} fragmentation_details_t;

В таком случае при доступе к полям нам не нужно будет добавлять вложенную структуру! НА мой взгляд, это серьезное удобство и ради него я согласен пойти на использование расширений стандарта :) Хотя, вроде бы в С11 эта фича в стандарте! Но не в С++, увы, пока такой фичи в С++ нету.

Учтите, в режиме pedantic будут жалобы "../isolated_example.cpp:13:5: warning: ISO C++ prohibits anonymous structs [-Wpedantic]" и "warning: anonymous structs are a GNU extension [-Wgnu-anonymous-struct]".




No comments :

Post a Comment

Note: only a member of this blog may post a comment.