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

今天在写一个协议分析程序时,使用了位域,因为协议的一个数据包有的参数并不是占据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
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容