Структура памяти структуры, имеющей битовые поля
У меня есть эта структура C: (представляет датаграмму IP)
struct ip_dgram
{
unsigned int ver : 4;
unsigned int hlen : 4;
unsigned int stype : 8;
unsigned int tlen : 16;
unsigned int fid : 16;
unsigned int flags : 3;
unsigned int foff : 13;
unsigned int ttl : 8;
unsigned int pcol : 8;
unsigned int chksm : 16;
unsigned int src : 32;
unsigned int des : 32;
unsigned char opt[40];
};
Я присваиваю ему значения, а затем печатаю макет памяти в 16-битных словах, например так:
//prints 16 bits at a time
void print_dgram(struct ip_dgram dgram)
{
unsigned short int* ptr = (unsigned short int*)&dgram;
int i,j;
//print only 10 words
for(i=0 ; i<10 ; i++)
{
for(j=15 ; j>=0 ; j--)
{
if( (*ptr) & (1<<j) ) printf("1");
else printf("0");
if(j%8==0)printf(" ");
}
ptr++;
printf("\n");
}
}
int main()
{
struct ip_dgram dgram;
dgram.ver = 4;
dgram.hlen = 5;
dgram.stype = 0;
dgram.tlen = 28;
dgram.fid = 1;
dgram.flags = 0;
dgram.foff = 0;
dgram.ttl = 4;
dgram.pcol = 17;
dgram.chksm = 0;
dgram.src = (unsigned int)htonl(inet_addr("10.12.14.5"));
dgram.des = (unsigned int)htonl(inet_addr("12.6.7.9"));
print_dgram(dgram);
return 0;
}
Я получаю этот вывод:
00000000 01010100
00000000 00011100
00000000 00000001
00000000 00000000
00010001 00000100
00000000 00000000
00001110 00000101
00001010 00001100
00000111 00001001
00001100 00000110
Но я ожидаю этого:
Вывод частично правильный; где-то байты и кусочки кажутся взаимозаменяемыми. Есть ли здесь проблема с порядком байтов? Разве битовые поля не подходят для этой цели? Я действительно не знаю. Любая помощь? Заранее спасибо!
7 ответов
Нет, битовые поля не подходят для этой цели. Макет зависит от компилятора.
Как правило, не рекомендуется использовать битовые поля для данных, в которых вы хотите управлять результирующим макетом, если у вас нет (специфичных для компилятора) средств, таких как #pragma
с, чтобы сделать это.
Наилучший способ, вероятно, состоит в том, чтобы реализовать это без битовых полей, т.е. выполнить необходимые побитовые операции самостоятельно. Это раздражает, но гораздо проще, чем найти способ исправить это. Кроме того, он не зависит от платформы.
Определите заголовок как просто массив 16-битных слов, и тогда вы сможете достаточно легко вычислить контрольную сумму.
Стандарт C11 гласит:
Реализация может выделить любой адресуемый блок памяти, достаточно большой для хранения битового поля. Если остается достаточно места, битовое поле, которое непосредственно следует за другим битовым полем в структуре, должно быть упаковано в смежные биты той же единицы. Если остается недостаточно места, определяется, будет ли битовое поле, которое не помещается, в следующий блок или перекрывает соседние блоки, определяется реализацией. Порядок распределения битовых полей внутри блока (от старшего к младшему или от младшего к старшему) определяется реализацией.
Я почти уверен, что это нежелательно, так как это означает, что между вашими полями может быть заполнение, и что вы не можете контролировать порядок своих полей. Не только это, но вы находитесь в прихоти реализации с точки зрения сетевого порядка байтов. Кроме того, представьте, если unsigned int
только 16 бит, и вы просите вписать в него 32-битное поле:
Выражение, которое определяет ширину битового поля, должно быть выражением целочисленной константы с неотрицательным значением, которое не превышает ширину объекта того типа, который был бы указан, если бы двоеточие и выражение были опущены.
Я предлагаю использовать массив unsigned char
с вместо структуры. Таким образом, вы гарантированно контролируете заполнение и порядок сетевых байтов. Начните с размера в битах, который вы хотите, чтобы ваша структура, в общей сложности. Я предполагаю, что вы объявляете это в константе, такой как IP_PACKET_BITCOUNT: typedef unsigned char ip_packet[(IP_PACKET_BITCOUNT / CHAR_BIT) + (IP_PACKET_BITCOUNT % CHAR_BIT > 0)];
Написать функцию, void set_bits(ip_packet p, size_t bitfield_offset, size_t bitfield_width, unsigned char *value) { ... }
который позволяет устанавливать биты, начиная с p[bitfield_offset / CHAR_BIT]
немного bitfield_offset % CHARBIT
до битов, найденных в значении, до bitfield_width
биты в длину. Это будет самая сложная часть вашей задачи.
Затем вы можете определить идентификаторы для VER_OFFSET 0 и VER_WIDTH 4, HLEN_OFFSET 4 и HLEN_WIDTH 4 и т. Д., Чтобы модификация массива казалась менее безболезненной.
Хотя вопрос задан давно, ответа нет с объяснением вашего результата. Я отвечу на это, надеюсь, это будет кому-то полезно.
Я проиллюстрирую ошибку, используя первые 16 бит вашей структуры данных.
Обратите внимание: это объяснение гарантировано, чтобы быть верным только с набором вашего процессора и компилятора. Если любое из этих изменений, поведение может измениться.
Поля:
unsigned int ver : 4; unsigned int hlen : 4; unsigned int stype : 8;
Назначено:
dgram.ver = 4; dgram.hlen = 5; dgram.stype = 0;
Компилятор начинает присваивать битовые поля, начиная со смещения 0. Это означает, что первый байт вашей структуры данных хранится в памяти как:
Bit offset: 7 4 0
-------------
| 5 | 4 |
-------------
Первые 16 битов после назначения выглядят так:
Bit offset: 15 12 8 4 0
-------------------------
| 5 | 4 | 0 | 0 |
-------------------------
Memory Address: 100 101
Вы используете 16-значный беззнаковый указатель на адрес 100 разыменования памяти. В результате адрес 100 обрабатывается как младший бит 16-разрядного числа. И 101 рассматривается как MSB 16-разрядного числа.
Если вы напечатаете *ptr в шестнадцатеричном виде, вы увидите это:
*ptr = 0x0054
Ваш цикл работает на этом 16-битном значении и, следовательно, вы получите:
00000000 0101 0100
-------- ---- ----
0 5 4
Решение: измените порядок элементов на
unsigned int hlen : 4;
unsigned int ver : 4;
unsigned int stype : 8;
И используйте беззнаковый указатель char * для перемещения и печати значений. Он должен работать.
Пожалуйста, обратите внимание, как уже говорили другие, это поведение зависит от платформы и компилятора. Если какие-либо из этих изменений будут изменены, вам необходимо проверить правильность расположения памяти в вашей структуре данных.
Для китайских пользователей, я думаю, вы можете обратиться в блог для получения более подробной информации, действительно хорошо.
Таким образом, из-за порядка байтов существует порядок байтов, а также порядок битов. Битовый порядок - это порядок, в котором каждый бит одного байта сохраняется в памяти. Порядок битов имеет то же правило с порядком байтов в смысле проблемы байтов.
Для вашего изображения он разработан в сетевом порядке с прямым порядком байтов. Итак, ваше определение структуры действительно для прямого порядка байтов. Согласно вашему выводу, ваш компьютер имеет прямой порядок байтов, поэтому вам нужно изменить порядок полей структуры при использовании.
Способ отображения каждого бита неверен, поскольку при получении с помощью char порядок битов изменился с машинного порядка (в вашем случае с прямым порядком байтов) на нормальный порядок, который мы используем человеком. Вы можете изменить его, как указано ниже в указанном блоге.
void
dump_native_bits_storage_layout(unsigned char *p, int bytes_num)
{
union flag_t {
unsigned char c;
struct base_flag_t {
unsigned int p7:1,
p6:1,
p5:1,
p4:1,
p3:1,
p2:1,
p1:1,
p0:1;
} base;
} f;
for (int i = 0; i < bytes_num; i++) {
f.c = *(p + i);
printf("%d%d%d%d %d%d%d%d ",
f.base.p7,
f.base.p6,
f.base.p5,
f.base.p4,
f.base.p3,
f.base.p2,
f.base.p1,
f.base.p0);
}
printf("\n");
}
//prints 16 bits at a time
void print_dgram(struct ip_dgram dgram)
{
unsigned char* ptr = (unsigned short int*)&dgram;
int i,j;
//print only 10 words
for(i=0 ; i<10 ; i++)
{
dump_native_bits_storage_layout(ptr, 1);
/* for(j=7 ; j>=0 ; j--)
{
if( (*ptr) & (1<<j) ) printf("1");
else printf("0");
if(j%8==0)printf(" ");
}*/
ptr++;
//printf("\n");
}
}
@размотать
Типичным случаем использования битовых полей является интерпретация/эмуляция байтового кода или инструкций ЦП с заданной компоновкой. «Не используй это, потому что ты не можешь это контролировать» — вот ответ для детей.
@Брюс
Для Intel/GCC я вижу упакованную битовую раскладку LITTLE ENDIAN, т.е.
struct ip_dgram
поле
ver
представлен битами 0..3, поле
hlen
представлен битами 4..7...
Для корректности работы необходимо сверять расположение памяти с вашим дизайном во время выполнения.
struct ModelIndicator
{
int a:4;
int b:4;
int c:4;
};
union UModelIndicator
{
ModelIndicator i;
int v;
};
// test packed little endian
static bool verifyLayoutModel()
{
UModelIndicator um;
um.v = 0;
um.i.a = 2; // 0..3
um.i.b = 3; // 4..7
um.i.c = 9; // 8..11
return um.v = (9 << 8) + (3 << 4) + 2;
}
int main()
{
if (!verifyLayoutModel())
{
std::cerr << "Invalid memory layout" << std::endl;
return -1;
}
// ...
}
Самое раннее, когда вышеуказанный тест не пройден, вам необходимо рассмотреть прагмы компилятора или соответствующим образом настроить свои структуры, соответственно. проверитьМодельМакета().
Я согласен с тем, что сказал раскручивать. Битовые поля зависят от компилятора.
Если вам нужно, чтобы биты были в определенном порядке, упакуйте данные в указатель на массив символов. Увеличьте размер буфера до размера упаковываемого элемента. Упакуйте следующий элемент.
pack( char** buffer )
{
if ( buffer & *buffer )
{
//pack ver
//assign first 4 bits to 4.
*((UInt4*) *buffer ) = 4;
*buffer += sizeof(UInt4);
//assign next 4 bits to 5
*((UInt4*) *buffer ) = 5;
*buffer += sizeof(UInt4);
... continue packing
}
}
Зависит от компилятора или нет. Это зависит от того, хотите ли вы написать очень быструю программу или если вы хотите, чтобы она работала с разными компиляторами. Чтобы написать для C быстрое компактное приложение, используйте структуру с битовыми полями /. Если вы хотите медленную программу общего назначения, используйте длинный код.