我有这个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
但我希望如此:
输出部分正确;在某个地方,字节和半字节似乎是互换的。这里有一些字节序问题吗?位字段是否不适合此目的?我真的不知道。有帮助吗?提前谢谢!
答案 0 :(得分:7)
不,位域不利于此目的。布局依赖于编译器。
对于要控制结果布局的数据,使用位域通常不是一个好主意,除非你有(编译器特定的)方法,例如#pragma
,这样做。
最好的方法可能是在没有位域的情况下实现它,即通过自己进行所需的按位运算。这很烦人,但比某种方式更容易找到解决这个问题的方法。此外,它与平台无关。
将标题定义为16位字的数组,然后您可以轻松地计算校验和。
答案 1 :(得分:2)
C11标准说:
实现可以分配任何可寻址的存储单元 足以容纳一个位域。如果剩余足够的空间,那就是一个位域 紧接着结构中的另一个位字段应该被打包 进入同一单元的相邻位。如果剩余空间不足, 是否将不适合的位域放入下一个单元或 重叠相邻单元是实现定义的。的顺序 单位内的位域分配(高位到低位或 低阶到高阶)是实现定义的。
我很确定这是不受欢迎的,因为这意味着您的字段之间可能存在填充,并且您无法控制字段的顺序。不仅如此,您还可以在网络字节顺序方面随心所欲地实现。另外,假设unsigned int
只有16位,并且要求在其中插入32位位域:
指定位字段宽度的表达式应为a 具有非负值的整数常量表达式 超过将指定类型的对象的宽度 冒号和表达式被省略。
我建议使用unsigned char
s数组而不是结构。这样您就可以保证控制填充和网络字节顺序。从您想要结构的位数开始,总计。我假设你在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等定义标识符,以使数组的修改看起来不那么轻松。
答案 2 :(得分:1)
虽然长时间回答问题,但对结果的解释没有答案。我会回答它,希望它对某人有用。
我将使用您数据结构的前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位数的LSB。并且101被视为16位数的MSB。
如果你用十六进制打印* ptr,你会看到:
*ptr = 0x0054
您的循环正在运行此16位值,因此您得到:
00000000 0101 0100
-------- ---- ----
0 5 4
<强>解决方案:强> 将元素的顺序更改为
unsigned int hlen : 4;
unsigned int ver : 4;
unsigned int stype : 8;
并使用unsigned char * pointer 来遍历和打印值。 它应该工作。
请注意,正如其他人所说,此行为是特定于平台和编译器的。如果这些更改中的任何一项,您需要验证数据结构的内存布局是否正确。
答案 3 :(得分:0)
对于中国用户,我认为您可以参考blog以获得更多详细信息,真的很好。
总而言之,由于字节顺序,既有字节顺序又有比特顺序。位顺序是一个字节的每个位如何保存在内存中的顺序。就字节顺序而言,位顺序与字节顺序具有相同的规则。
对于您的图片,它是按照大端网络顺序设计的。因此,您的结构定义实际上是针对大字节序的。根据您的输出,您的PC是低位字节序,因此在使用时需要更改结构字段顺序。
显示每个位的方法是错误的,因为当通过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");
}
}
答案 4 :(得分:0)
@unwind
位域的一个典型用例是解释/模拟具有给定布局的字节码或 CPU 指令。 “不要用它,因为你无法控制它”是孩子们的答案。
@布鲁斯
对于 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;
}
// ...
}
最早,当上述测试失败时,您需要考虑编译器编译指示或相应地调整您的结构,分别是。 verifyLayoutModel().
答案 5 :(得分:-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
}
}
答案 6 :(得分:-1)
编译器是否依赖,取决于您是想编写一个非常快的程序,还是想要一个适用于不同编译器的程序。要为快速,紧凑的应用程序编写C,请使用带有位字段/的结构。如果你想要一个缓慢的通用程序,可以长代码。