找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 6707|回复: 3

【C】位域的相邻顺序

[复制链接]

1109

主题

1649

回帖

7万

积分

用户组: 管理员

一只技术宅

UID
1
精华
244
威望
743 点
宅币
24180 个
贡献
46222 次
宅之契约
0 份
在线时间
2294 小时
注册时间
2014-1-26
发表于 2018-11-7 16:05:14 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
位域是一种应该被避开使用的东西,它破坏你的代码的可移植性,降低可读性,影响优化效果,并且增加维护难度。尤其是对于现今视内存如粪土的时代,能不用就不用。
然而如果我非得去使用呢?比如读取一个SD卡的CSD寄存器(Card-Specific Data),它里面就有很多指定位存储的信息:
csd.png
(图上代码来源于SdFat库的实现:https://github.com/greiman/SdFat ... src/SdCard/SdInfo.h
对于这种情况,存在一个问题:大小端的不同,会导致位域的位置不同。比如reserved1和csd_ver,谁在前谁在后,光看上述代码片段,是不明确的。

事实上,对于位域的前后顺序问题,C99的定义如下(C99 standard 6.7.2.1/10 - "Structure and union specifiers"):
An implementation may allocate any addressable storage unit large enough to hold a bitfield. If enough space remains, a bit-field that immediately follows another bit-field in a structure shall be packed into adjacent bits of the same unit. If insufficient space remains, whether a bit-field that does not fit is put into the next unit or overlaps adjacent units is implementation-defined. The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined. The alignment of the addressable storage unit is unspecified.
也就是“如果当前单元还有足够的剩余空间,下一个位域会紧随上一个位域,如果剩余空间不够,这个位域要么被放到下一个单元,要么跨越当前这个单元到下一个单元,取决于实现定义。一个单元里的位域的排列顺序是根据具体实现来定义的。而对于每个可寻址单元的对齐,则是未指定的。”

那么“下一个”和“上一个”到底是哪个方向呢?

而GCC则在它的手册页面“Using the GNU Compiler Collection (GCC): Structures unions enumerations and bit-fields implementation”说:“Determined by ABI

GCC的邮件通讯列表里有提示说:
Bit-fields are always assigned to the first available bit, possibly constrained by other factors, such as alignment. That means that they start at the low order bit for little-endian, and the high order bit for big-endian. This is the "right" way to do things. It is very unusual for a compiler to do this differently.
翻译过来就是“位域永远被放到从第一个可以使用的位开始的位串。可能受到其他因素的限制,例如对齐。这意味着它在小端平台从最低bit开始分配,而从大端平台则从最高bit开始分配。这是最‘正确’的做法。对于编译器而言,用不同的方式做这件事是很不寻常的。”

请看下例代码:
  1. #include <stdio.h>

  2. typedef struct tagT
  3. {
  4.         int a:4;
  5.         int b:4;
  6.         int c:8;
  7.         int d:16;
  8. }T;

  9. int main()
  10. {
  11.         char data[]={0x12,0x34,0x56,0x78};
  12.         T *t = (T*)data;
  13.         printf("a =0x%x\n" ,t->a);
  14.         printf("b =0x%x\n" ,t->b);
  15.         printf("c =0x%x\n" ,t->c);
  16.         printf("d =0x%x\n" ,t->d);

  17.         return 0;
  18. }
复制代码
在大小端不同的平台上打印出来的结果是不一样的:
  1. // - 大端 :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
  2. a =0x1
  3. b =0x2
  4. c =0x34
  5. d =0x5678
  6. // - 小端 : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
  7. a =0x2
  8. b =0x1
  9. c =0x34
  10. d =0x7856
复制代码
可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台,顺序靠前的位域变量则更靠左一些。

我们再来举一个例子:
  1. union
  2. {
  3.         unsigned short value;
  4.         unsigned char byte[2];
  5.         struct
  6.         {
  7.                 unsigned short a : 1;
  8.                 unsigned short b : 2;
  9.                 unsigned short c : 3;
  10.                 unsigned short d : 4;
  11.                 unsigned short e : 5;
  12.         } field;
  13. } v;

  14. v.field.a = 1;
  15. v.field.b = 2;
  16. v.field.c = 3;
  17. v.field.d = 4;
  18. v.field.e = 5;
复制代码
对于上述代码,在大端平台和小端平台分别会得到什么呢?如下图:
小端平台:
littleendian-2.jpg
大端平台:
bigendian-2.jpg

事实上,如果你要在想要跨大端小端平台的代码上写一个靠谱的使用了位域的结构体的话,你需要通过来判断当前是大端还是小端,然后用不同的顺序去写结构体。典型的例子就是Linux内ip_hdr结构体的定义:
  1. struct iphdr
  2.   {
  3. #if __BYTE_ORDER == __LITTLE_ENDIAN
  4.     unsigned int ihl:4;
  5.     unsigned int version:4;
  6. #elif __BYTE_ORDER == __BIG_ENDIAN
  7.     unsigned int version:4;
  8.     unsigned int ihl:4;
  9. #else
  10. # error "Please fix <bits/endian.h>"
  11. #endif
  12.     u_int8_t tos;
  13.     u_int16_t tot_len;
  14.     u_int16_t id;
  15.     u_int16_t frag_off;
  16.     u_int8_t ttl;
  17.     u_int8_t protocol;
  18.     u_int16_t check;
  19.     u_int32_t saddr;
  20.     u_int32_t daddr;
  21.     /*The options start here. */
  22.   };
复制代码
参考资料:
How Endianness Effects Bitfield Packing
C/C++: Force Bit Field Order and Alignment

本帖被以下淘专辑推荐:

回复

使用道具 举报

29

主题

315

回帖

1561

积分

用户组: 上·技术宅

UID
3808
精华
11
威望
105 点
宅币
702 个
贡献
165 次
宅之契约
0 份
在线时间
404 小时
注册时间
2018-5-6
发表于 2018-11-15 00:52:45 | 显示全部楼层
啊,学习到了啊,文中这句:“可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台,顺序靠前的位域变量则更靠左一些。”
感觉加上说明:内存中  “低地址------->高地址”就更加明白大端小端了
但是小弟不知道怎么在win32汇编中实现这个位域的数据结构,难道这个只有C/C++才有的么?
Passion Coding!
回复 赞! 靠!

使用道具 举报

29

主题

315

回帖

1561

积分

用户组: 上·技术宅

UID
3808
精华
11
威望
105 点
宅币
702 个
贡献
165 次
宅之契约
0 份
在线时间
404 小时
注册时间
2018-5-6
发表于 2018-11-15 02:08:48 | 显示全部楼层
watermelon 发表于 2018-11-15 00:52
啊,学习到了啊,文中这句:“可以看出:在小端平台上,顺序靠前的位域变量它的位置更靠右。而对于大端平台 ...

了解了,tangptr大佬说有record关键字,的确可以。
Passion Coding!
回复 赞! 靠!

使用道具 举报

1

主题

16

回帖

44

积分

用户组: 初·技术宅

UID
4492
精华
0
威望
2 点
宅币
23 个
贡献
0 次
宅之契约
0 份
在线时间
0 小时
注册时间
2018-11-20
发表于 2018-11-20 18:13:35 | 显示全部楼层
学习一下
回复

使用道具 举报

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2024-3-19 10:02 , Processed in 0.043273 second(s), 37 queries , Gzip On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表