美文网首页
使用C语言位域的陷阱:大端与小端

使用C语言位域的陷阱:大端与小端

作者: 哈莉_奎茵 | 来源:发表于2017-11-22 15:19 被阅读0次

今天在写一个协议分析程序时,使用了位域,因为协议的一个数据包有的参数并不是占据n个字节(bytes),而是占据n位(bits)。
比如有1个字节包含了4个参数,分别占据的位数为3,1,1,2,1,我就在我的数据包结构体中定义了如下位域

struct Packet
{
    // ...
    uint8_t a : 3;
    uint8_t b : 1;
    uint8_t c : 1;
    uint8_t d : 2;
    uint8_t e : 1;
    // ...
};

调试的时候发现结果不对,然后写了个测试

struct Widget
{
    uint8_t a : 4;
    uint8_t b : 3;
    uint8_t c : 1;

    void show()
    {
        printf("%d, %d, %d\n", a, b, c);
    }
};

void func()
{
    uint8_t x = 0b11100110;
    auto p = (Widget*)&x;
    p->show();  // output: 6, 6, 1
}

a表示前4位,b表示中间3位,c表示后面1位,直观地来看,a是1110(14),b是011(3),c是0。但结果并非直观看到的那样。
问题出在内存布局方面,windows系统是小端布局,即低地址存放低字节,也就是位域的顺序是反过来的,即a是0110(6),b是110(6),c是1。
需要注意的是,大端小端是以字节为单位的,所以在内存中,x并非存储为
0 1 1 0 0 1 1 1 (从左向右地址依次增加)
而是针对位域而言,低地址的成员(a)对应的是字节的后半部分。
引用《C++ Primer》第5版19.8.1的说明

当一个程序需要向其他程序或硬件设备传递二进制数据时,通常会用到位域。
位域在内存中的布局是与机器相关的。

其中19.8这一小节的标题是固有的不可移植的特性
因此使用位域的时候,必须明确程序运行的机器是大端还是小端,再在代码中定义各位域的具体顺序。像Linux中/usr/include/netinet/tcp.h中定义TCP包结构时就使用了位域

# if __BYTE_ORDER == __LITTLE_ENDIAN
    u_int16_t res1:4;
    u_int16_t doff:4;
    u_int16_t fin:1;
    u_int16_t syn:1;
    u_int16_t rst:1;
    u_int16_t psh:1;
    u_int16_t ack:1;
    u_int16_t urg:1;
    u_int16_t res2:2;
# elif __BYTE_ORDER == __BIG_ENDIAN
    u_int16_t doff:4;
    u_int16_t res1:4;
    u_int16_t res2:2;
    u_int16_t urg:1;
    u_int16_t ack:1;
    u_int16_t psh:1;
    u_int16_t rst:1;
    u_int16_t syn:1;
    u_int16_t fin:1;
# else

可以看到它是用系统定义的宏来区分大端还是小端。通过代码跳转可以找到__BYTE_ORDER宏的定义

 #define __BYTE_ORDER __LITTLE_ENDIAN    

或者,在结构体内不使用位域,而是使用字节来存储(比如8位),再通过位运算来计算出字节具体若干位对应的值,比如刚才的Widget类可以改写成下面这样来避免机器大小端影响。

struct Widget
{
    uint8_t x;

    uint8_t a() const { return (x & 0xf0) >> 4; }
    uint8_t b() const { return (x & 0x0e) >> 1; }
    uint8_t c() const { return (x & 0x01); }

    void show()
    {
        printf("%d, %d, %d\n", a(), b(), c());
    }
};

void func()
{
    uint8_t x = 0b11100110;
    auto p = (Widget*)&x;
    p->show();  // output: 14, 3, 0
}

相关文章

  • 使用C语言位域的陷阱:大端与小端

    今天在写一个协议分析程序时,使用了位域,因为协议的一个数据包有的参数并不是占据n个字节(bytes),而是占据n位...

  • Bitmap ARGB8888 BGRA8888 RGB24 R

    Android使用Java语言,Java默认使用大端字节序,c/c++默认使用小端字节序,当我们在jni中需要使用...

  • 字节对齐与大端小端与内存区域划分

    字节对齐 C语言字节对齐C语言字节对齐/7213465 大端小端 字节序(大小端)详解从高低地址和高低位开始理解(...

  • 大小端

    Java与平台无关默认是大端,网络上传输数据普遍采用大端C语言默认是小端数据在计算机中存储的大小端两种,以十进制数...

  • 10_struct和union分析

    关键词:struct、结构体与柔性数组、union、小端模式和大端模式 1. struct的小秘密 C语言中str...

  • C: 判断大小端

    大端与小端 大端与小端指的是多字节的数值在内存中的存储形式,数值的起始存储在内存的高序地址则为大端,反之为小端: ...

  • 网络编程之大小端

    大端&小端 用C/C++写网络程序时,要注意字节的网络顺序和主机顺序的问题。 大端:高位在前,低位在后 小端:高位...

  • 字节序:大端法和小端法

    大端和小端 大端法:高位保存在低地址中。 小端法:高位存放在高地址中, 程序判断大端法还是小端法 大端和小端法对程...

  • C语言位运算

    C语言位运算_C语言中文网 C语言位域(位段)_C语言中文网

  • 16位数的高低八位

    大小端高位字节在前,低位字节在后称之为大端。反之,为小端。 以下都是基于大端16位二进制数来说,小端反之。 取低八...

网友评论

      本文标题:使用C语言位域的陷阱:大端与小端

      本文链接:https://www.haomeiwen.com/subject/vrnnvxtx.html