Битовые поля и выравнивание

Пытаюсь упаковать данные в пакет. Этот пакет должен быть 64-битным. У меня есть это:

typedef union {
  uint64_t raw;
  struct {
    unsigned int magic    : 8;
    unsigned int parity   : 1;
    unsigned int stype    : 8;
    unsigned int sid      : 8;
    unsigned int mlength  : 31;
    unsigned int message  : 8;
  } spacket;
} packet_t;

Но кажется, что выравнивание не гарантируется. Потому что, когда я запускаю это:

#include <strings.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>

const char *number_to_binary(uint64_t x)
{
    static char b[65];
    b[64] = '\0';

    uint64_t z;
    int w = 0;
    for (z = 1; w < 64; z <<= 1, ++w)
    {
        b[w] = ((x & z) == z) ? '1' : '0';
    }

    return b;
}

int main(void)
{
  packet_t ipacket;
  bzero(&ipacket, sizeof(packet_t));
  ipacket.spacket.magic = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.parity = 1;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.stype = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.sid = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.mlength = 2147483647;
  printf("%s\n", number_to_binary(ipacket.raw));
  ipacket.spacket.message = 255;
  printf("%s\n", number_to_binary(ipacket.raw));
}

Я получаю (старший порядок байтов):

1111111100000000000000000000000000000000000000000000000000000000
1111111110000000000000000000000000000000000000000000000000000000
1111111111111111100000000000000000000000000000000000000000000000
1111111111111111111111111000000000000000000000000000000000000000
1111111111111111111111111000000011111111111111111111111111111110
1111111111111111111111111000000011111111111111111111111111111110

Мое поле .mlength потеряно где-то в правой части, хотя оно должно быть прямо рядом с полем .sid.

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

24 бита, по-видимому, являются максимальным размером, который поле .mlength может принять до того, как поле .message будет удалено.


person Luke Skywalker    schedule 16.04.2016    source источник
comment
Как правило, вы не можете полагаться на то, как расположены данные в битовом поле. Если вам нужно упаковать несколько данных в слово, вы должны выполнить сортировку вручную.   -  person fuz    schedule 16.04.2016
comment
ваша структура 32 * 6 бит... Вместо этого вы должны использовать символы.   -  person xvan    schedule 16.04.2016
comment
Не полагайтесь на определенную структуру памяти для структур данных C. Слишком много параметров, определяемых реализацией и специфичных для компилятора. Как писал @FUZxxl, используйте правильную сортировку. С хорошим компилятором это не обязательно медленнее.   -  person too honest for this site    schedule 16.04.2016
comment
@xvan: использование стандартных типов - плохая идея. А char еще хуже.   -  person too honest for this site    schedule 16.04.2016
comment
@Olaf Использование стандартных типов неплохая идея, если вы знаете, что делаете.   -  person fuz    schedule 16.04.2016
comment
@FUZxxl: подход OP с использованием типов фиксированной ширины уже является хорошей идеей. Как изменить это в пользу использования типов с диапазоном и подписанностью, определяемыми реализацией?   -  person too honest for this site    schedule 17.04.2016
comment
@Olaf Потому что типы с фиксированной шириной могут быть недоступны везде и могут быть не нужны.   -  person fuz    schedule 17.04.2016
comment
@FUZxxl: этот вопрос касается C и помечен как C, что подразумевает стандартный C (см. информацию). А стандарт C требует, чтобы все реализации предоставляли этот заголовок и определенные типы. Если uint8_t и т. д. недоступны, причина в том, что ваша архитектура не поддерживает требуемую ширину или (для подписанных типов) представление. Что в конечном итоге сломает любой код, полагающийся на ширину стандартных типов. Таким образом, использование (u)intN_t является еще одной системой безопасности для вашей реализации.   -  person too honest for this site    schedule 17.04.2016


Ответы (2)


Почти все, что касается компоновки битовых полей, определяется реализацией в стандарте, как вы найдете из множества других вопросов по этому вопросу на SO. (Среди прочего вы можете просмотреть вопросы о битовых полях и особенно Управление памятью битового поля в C).

Если вы хотите, чтобы ваши битовые поля были упакованы в 64-битные, вам придется поверить, что ваш компилятор позволяет вам использовать 64-битные типы для полей, а затем использовать:

typedef union {
  uint64_t raw;
  struct {
    uint64_t magic    : 8;
    uint64_t parity   : 1;
    uint64_t stype    : 8;
    uint64_t sid      : 8;
    uint64_t mlength  : 31;
    uint64_t message  : 8;
  } spacket;
} packet_t;

Как было написано изначально, по одной из вероятных (общих) схем ваши битовые поля будут разбиты на новые 32-битные слова, когда в текущем не останется места. То есть magic, parity, stype и sid будут занимать 25 бит; в 32-битном unsigned int недостаточно места для хранения еще 31 бита, поэтому mlength хранится в следующем unsigned int, а в этом блоке недостаточно места для хранения message, так что он хранится в третьем unsigned int ед. Это даст вам структуру, занимающую 3 * sizeof(unsigned int) или 12 байтов, а объединение займет 16 байтов из-за требований выравнивания по uint64_t.

Обратите внимание, что стандарт не гарантирует, что то, что я покажу, будет работать. Однако под многими компиляторами это, вероятно, будет работать. (В частности, он работает с GCC 5.3.0 в Mac OS X 10.11.4.)

person Jonathan Leffler    schedule 16.04.2016
comment
Но даже в этом случае вы не можете зависеть от порядка расположения битовых полей. Измените свой компилятор, и все может измениться. И, если быть точным, также реализация определяет, должно ли битовое поле разбиваться на новый элемент структуры, когда оно не может вписаться в текущий - оно может охватывать элементы. - person Andrew Henle; 16.04.2016
comment
Как я уже сказал, практически все, кроме их существования, определяется реализацией. На вопрос управление памятью битового поля в C есть ответ (из YT), цитирующий стандарт, широко, на предмет битовых полей. - person Jonathan Leffler; 16.04.2016

В зависимости от вашей архитектуры и/или компилятора ваши данные будут выровнены по разным размерам. Из ваших наблюдений я бы предположил, что вы видите последствия 32-битного выравнивания. Если вы посмотрите на размер вашего объединения, и он больше 8 байт (64 бита), данные будут дополнены для выравнивания.

С 32-битным выравниванием mlength и сообщение смогут оставаться рядом друг с другом только в том случае, если их сумма меньше или равна 32 битам. Это, вероятно, то, что вы видите с вашим 24-битным ограничением.

Если вы хотите, чтобы ваша структура занимала только 64 бита с 32-битным выравниванием, вам придется немного ее изменить. Однобитовая четность должна быть рядом с 31-битной длиной m, а ваши 4 8-битные переменные должны быть сгруппированы вместе.

person Henrik Carlqvist    schedule 16.04.2016