具有位域的结构的存储器布局

Bru*_*uce 7 c unix ip struct bit-fields

我有这个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];
};
Run Code Online (Sandbox Code Playgroud)

我正在为它赋值,然后以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;
}
Run Code Online (Sandbox Code Playgroud)

我得到这个输出:

00000000 01010100 
00000000 00011100 
00000000 00000001 
00000000 00000000 
00010001 00000100 
00000000 00000000 
00001110 00000101 
00001010 00001100 
00000111 00001001 
00001100 00000110
Run Code Online (Sandbox Code Playgroud)

但我希望这个:

在此输入图像描述

输出部分正确; 在某个地方,字节和半字节似乎是互换的.这里有一些字节序问题吗?位字段是否不适合此目的?我真的不知道.有帮助吗?提前致谢!

unw*_*ind 8

不,位域不利于此目的.布局依赖于编译器.

对于要控制结果布局的数据,使用位域通常不是一个好主意,除非你有(编译器特定的)手段,比如#pragmas.

最好的方法可能是在没有位域的情况下实现它,即通过自己进行所需的按位运算.这很烦人,但比某种方式更容易找到解决这个问题的方法.此外,它与平台无关.

将标头定义为16位字的数组,然后您可以轻松地计算校验和.


aut*_*tic 6

C11标准说:

实现可以分配足够大的任何可寻址存储单元来保存位字段。如果剩余足够的空间,则结构中紧跟在另一个位字段之后的位字段应被打包到同一单元的相邻位中。如果剩余空间不足,则不适合的位字段是否放入下一个单元或与相邻单元重叠是实现定义的。单元内位字段的分配顺序(高位到低位或低位到高位)是由实现定义的。

我很确定这是不可取的,因为这意味着字段之间可能存在填充,并且您无法控制字段的顺序。不仅如此,您还可以根据网络字节顺序随意实现。此外,想象一下如果 anunsigned int只有 16 位,并且您要求将 32 位位字段放入其中:

指定位域宽度的表达式应为具有非负值的整数常量表达式,该值不超过省略冒号和表达式时指定类型的对象的宽度。

我建议使用 s 数组unsigned char而不是结构体。这样您就可以保证对填充和网络字节顺序的控制。从您希望结构的总体大小(以位为单位)开始。我假设您在 IP_PACKET_BITCOUNT 等常量中声明这一点:typedef unsigned char ip_packet[(IP_PACKET_BITCOUNT / CHAR_BIT) + (IP_PACKET_BITCOUNT % CHAR_BIT > 0)];

编写一个函数,它允许您将从bitvoid set_bits(ip_packet p, size_t bitfield_offset, size_t bitfield_width, unsigned char *value) { ... }开始的位设置为 value 中找到的位,最大长度为 bits。这将是您任务中最复杂的部分。p[bitfield_offset / CHAR_BIT]bitfield_offset % CHARBITbitfield_width

然后,您可以定义 VER_OFFSET 0 和 VER_WIDTH 4、HLEN_OFFSET 4 和 HLEN_WIDTH 4 等标识符,以使数组的修改看起来更轻松。

  • 我有点困惑为什么位域是在标准中指定的。如果有一种可移植的方法来显式布局位域,那可能会非常有用;因为没有,所以我不确定通过例如以有效禁止 32 位编译器允许两个 12 位数字适合三字节结构的方式指定规则会获得什么。我可以理解不想*要求*编译器执行如此精细的打包,但我的理解是编译器不允许字段重叠两个字节边界。 (2认同)