字节序以及C语言struct中的位域

字节序

造成字节序问题出现的原因是各CPU架构下在内存中存储数值时使用的字节排序方式不一致。下图描述了内存字节存储位置和数值中字节位置的布局示意图:

数值高低字节的解释:对于十六进制数值0x123456,其最高字节的数值为0x12,最低字节的数值为0x56。

当使用不同的顺序将数值的字节流存储到内存时,将造成内存中不同的字节流顺序,这就是大小端字节序的本质原因。

大端序(BE, Big-Endian):内存地址从低到高的顺序依次存储数值中从高到低的字节。可简单的记为高字节在前,低字节在后(高字节存放在低地址,低字节存放在高地址)。

小端序(LE, Little-Endian):内存地址从低到高的顺序依次存储数值中从低到高的字节。可简单的记为低字节在前,高字节在后(低字节存放在低地址,高字节存放在高地址)。

大端序又叫做网络序,网络中发送报文时都是先发送低地址的字节(报文的高字节);接收报文时,最先收到的字节(报文的高字节)放在内存的低地址中。

网络中报文的字节流顺序为Byte 0、Byte 1、...,即:发送报文时先发送出去的字节为Byte 0,且Byte 0被认为是报文的最高字节,其存放在内存的低地址。网络中报文的字节流发送顺序就是网络序(大端序),这也是网络中传输报文时的字节流顺序。发送报文时,先发送低地址内存的字节,最后发送高地址内存的字节;接收报文时,最先收到的字节存放在内存的低地址中,最后收到的字节存放在内存的高地址中。

位序

字节内也存在大小端问题,与字节间的大小端问题类似(小端:低字节存储在低地址;大端:高字节存储在低地址)。将内存中单字节的存储空间可拆分成具有8个bit的位存储空间,单字节内的大小端问题变成在内存的位存储空间中先存放单字节数值的LSB还是单字节数值的MSB的问题。(注:操作系统中,最小的编址和寻址单元是字节本文为了方便描述给bit位在内存中也进行更细粒度的编址

注:每个字节都对应一个内存地址,内存寻址的最小单元是字节。

内存中的字节和位的存储空间排列方式(内存地址从左向右依次增加)如下所示:

单字节数值的位定义如下所示(数值中的bit0位是LSB(least significant bits)):

1)小端:单字节数值中的位排列方式如下(内存中位存储地址从左向右递增):

01234567

即存放在内存低地址位存储空间中的位是数值中的低位(LSB, least significant bits)。

2)大端:单字节数值中的位排列方式如下(内存中位存储地址从左向右递增):

76543210

即存放在内存低地址位存储空间中的位是数值中的高位(MSB, most significant bits)。

C语言中的位域定义中,为struct成员分配单字节数值中的bit位的逻辑为:按struct中成员的先后顺序,从内存中地址小的位存储空间开始分配

大小端设备上给struct位域成员分配的位存储空间与单字节数值中的bit位的对应关系为:

1) 小端:先从单字节数值中的LSB开始分配;

2) 大端:先从单字节数值中的MSB开始分配;

单字节内,大端的位域定义和小端的位域定义顺序相反时,代表了相同的结构体定义。对于定义如下的结构体(单个字节内的位域):

struct bit_order{
    uint32_t a:1;
    uint32_t b:2;
    uint32_t c:3;
    uint32_t d:2;
};

上述定义中在大小端设备上单字节数值中的bit与位域变量的对应关系:

为了兼容大小端的设备,应该按如下方式定义,数值类型中多余的位也需要用定义为保留变量,否则,大小端设备上变量对应的数值的bit位也不一样。

struct bit_order{

#if defined(__LITTLE_ENDIAN_BITFIELD)

    uint32_t a:1;

    uint32_t b:2;

    uint32_t c:3;

    uint32_t d:2;

    uint32_t rsv:24;

#else

    uint32_t rsv:24;

    uint32_t d:2;

    uint32_t c:3;

    uint32_t b:2;

    uint32_t a:1;

#endif

};

位序与网络收发包

在OSI七层模型的物理层中传输的数据流为bit流 [Byte0(bit0, bit1, ... , bit7), Byte1(bit0, bit1, ..., bit7), ...]:发送字节时,先发送字节的低bit位(LSB),再发送字节的高bit位(MSB);接收字节时,先接收到的bit为存放在字节的低bit位

网卡收包报文是以字节为单位的:发送时,先发送内存低地址的字节;接收时,将先收到的字节存储在内存地地址。因而,大小端影响了网卡发送报文时优先发送的字节。

对于单个字节的bit位收发,网卡是基于数值的bit位来处理的(而不管bit位是存储在内存低地址还是高地址),即先发送或接收数值的LSB。

从上文可知,小端设备将数值的LSB存储在内存地地址,大端设备将数值LSB存储在内存高地址。因此,网卡发送单字节的bit位时,小端设备先发送字节中内存低地址的bit位(即先从数值的LSB位开始),大端设备先发送字节中高地址的bit位(即先从数值的LSB位开始);网卡接收单字节的bit位时,小端设备将先收到的bit位存储在内存的低地址,大端设备将先收到的bit位存储在内存的高地址。

综上,对于单字节内的bit位,在网络收发包时,无需进行额外的位序转换网卡进行单字节的bit位收发时都是先从数值的LSB开始处理)。对于跨字节定义的位域,也只需处理字节序即可(方式见下文),但在位域定义时要基于大小端来定义位域(大小端的位域定义顺序刚好相反),用以保证定义的位域变量对应于数值中指定的位。

(1)不基于大小端定义结构,大端和小端设备定义的位域对应的数值的bit位不同

union test {

    uint16_t for_byte_order; 

    uint8_t byte[2]; 

    struct {

        uint16_t a:5;

        uint16_t b:5;

        uint16_t c:6;

    };

};

上述数据结构在大端和小端设备上的bit位存储布局和数值表示为:

(2)基于大小端定义结构,大端和小端设备定义的位域对应的数值的bit位相同

union test {

    uint16_t for_byte_order;// 用于转换字节序, 需与位域定义使用的整型类型相同 

    uint8_t byte[2]; 

    struct {

    #if defined(__LITTLE_ENDIAN_BITFIELD)

        uint16_t a:5;

        uint16_t b:5;

        uint16_t c:6;

    #elif defined (__BIG_ENDIAN_BITFIELD)

        uint16_t c:6;

        uint16_t b:5;

        uint16_t a:5;

    #else

    #error  "Please fix <asm/byteorder.h>"

    #endif

    };

};

上述数据结构在大端和小端设备上的bit位存储布局和数值表示为:

对于位域定义的结构体,基于大小端定义位域后,只需要处理字节序即可。对于上述定义的结构,使用for_byte_order进行字节序转换即可。字节序转换和bit流收发过程如下:

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BitSwimmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值