何时使用C中的位域?
在“为什么我们需要使用位域”的问题上,Google发现位域用于标志。 现在我好奇,是实际使用位场的唯一方法吗? 我们是否需要使用位域来节省空间?
从书中定义位域的方法:
struct { unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1; } flags;
为什么我们使用int? 占用了多less空间? 我很困惑,为什么我们使用int,但不小于或小于int的smith。 据我所知只有1位被占用在内存中,但不是整个无符号整型值。 这是对的吗?
现在我好奇,标志是实际使用位域的唯一方法吗?
不,标志不是唯一使用位域的方式。 它们也可以用来存储大于一位的值,尽pipe标志更常见。 例如:
typedef enum { NORTH = 0, EAST = 1, SOUTH = 2, WEST = 3 } directionValues; struct { unsigned int alice_dir : 2; unsigned int bob_dir : 2; } directions;
我们是否需要使用位域来节省空间?
位字段确实节省了空间。 它们还允许更简单的方法来设置不是字节alignment的值。 我们可以使用与在struct
设置字段相同的语法,而不是位移和使用按位操作。 这提高了可读性。 用一个bitfield,你可以写
directions.bob_dir = SOUTH;
但是,手动完成,你需要写下如下的东西:
#define BOB_OFFSET 2 directions &= ~(3<<BOB_OFFSET); // clear Bob's bits directions |= SOUTH<<BOB_OFFSET;
这种改进的可读性可以说比在这里和那里保存几个字节更重要。
为什么我们使用int? 占用了多less空间?
整个int
的空间被占用了。 我们使用int
因为在很多情况下,它并不重要。 如果单个值使用4个字节而不是1或2,则用户可能不会注意到。 对于一些平台来说,大小确实重要,而且可以使用占用较less空间的其他数据types( char
, short
, uint8_t
等)。
据我所知只有1位被占用在内存中,但不是整个无符号整型值。 这是对的吗?
不,这是不正确的。 整个unsigned int
将存在,即使你只使用8位。
一个相当不错的资源是C中的位域 。
基本的原因是减less使用的大小。 例如,如果你写:
struct { unsigned int is_keyword; unsigned int is_extern; unsigned int is_static; } flags;
您将至less使用3 * sizeof(unsigned int)
或12个字节来表示3个小标志,应该只需要3个位。
所以如果你写:
struct { unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1; } flags;
这使用了与一个unsigned int
相同的空间,所以4个字节。 在需要更多空间之前,您可以将32个一位字段放入结构中。
这相当于经典的家庭酿造位字段:
#define IS_KEYWORD 0x01 #define IS_EXTERN 0x02 #define IS_STATIC 0x04 unsigned int flags;
但是,位字段的语法更清晰,比较:
if (flags.is_keyword)
反对:
if (flags & IS_KEYWORD)
而且显然不容易出错。
硬件寄存器是位域通用的另一个地方。 如果你有一个32位寄存器,每一位都有一定的含义,你可以用一个位域来优雅地描述它。
这样的位域本质上是平台特定的。 在这种情况下,可移植性并不重要。
我们主要使用位域(尽pipe不是唯一的)标志结构 – 字节或单词(或者可能更大的东西),在这些字段或单词中我们试图打包小(通常是2状态)的(通常是相关的)信息。
在这些场景中,使用位域是因为它们正确地模拟了我们正在解决的问题:我们所处理的并不是真正的8位(或16位或24位或32位)数字,而是收集8(或16或24或32)相关但不同的信息。
我们使用位域解决的问题是“紧缩”信息具有可衡量的益处和/或“解包”信息不会受到惩罚的问题。 例如,如果你暴露出1个字节到8个引脚,每个引脚上的位通过已经印刷在电路板上的自己的总线,这样它就可以准确地到达应有的位置,那么一个位是理想的。 “打包”数据的好处是可以一次发送(如果总线的频率有限,我们的操作依赖于执行的频率,这是有用的),“拆包”数据的惩罚是不存在(或存在,但值得)。
另一方面,由于计算机体系结构通常工作的方式,所以在其他情况下(比如正常的程序stream程控制),我们不使用位域来实现布尔运算。 最常见的CPU不喜欢从内存中取一位 – 他们喜欢取字节或整数。 他们也不喜欢处理位 – 他们的指令经常在整数,字,内存地址等更大的东西上运行。
所以,当你试图对位进行操作的时候,取决于你或者编译器(取决于你写的是什么语言)来写出额外的操作,这些操作执行位掩码,并去除除了你实际想要的信息之外的所有东西的结构操作。 如果在“打包”信息时没有任何好处(而且在大多数情况下没有),那么使用位字段进行布尔运算只会在代码中引入开销和噪声。
为什么我们需要使用位字段?
当你想存储一些可以存储小于字节的数据的时候,这些types的数据可以使用位域来结构化。 在embedded式单词中,当任何寄存器的一个32位的世界对不同的单词具有不同的含义时,也可以使用位文件来使它们更具可读性。
我发现位域用于标志。 现在我好奇,是实际使用位场的唯一方法吗?
没有这不是唯一的方法。 你也可以用其他方式使用它。
我们是否需要使用位域来节省空间?
是。
据我所知只有1位被占用在内存中,但不是整个无符号整型值。 这是对的吗?
没有。 内存只能占用多个字节。
一个很好的用法是实现一个块来转换为base64或任何未alignment的数据结构。
struct { unsigned int e1:6; unsigned int e2:6; unsigned int e3:6; unsigned int e4:6; } base64enc; //I don't know if declaring a 4-byte array will have the same effect. struct { unsigned char d1; unsigned char d2; unsigned char d3; } base64dec; union base64chunk { struct base64enc enc; struct base64dec dec; }; base64chunk b64c; //you can assign 3 characters to b64c.enc, and get 4 0-63 codes from b64dec instantly.
这个例子有点幼稚,因为base64也必须考虑空终止(即一个长度不是1的string,以使l
%3为0)。 但作为访问未alignment的数据结构的示例。
另一个例子:使用这个特性将一个TCP数据包头部分解成它的组件 (或者你想讨论的其他networking协议数据包头部),虽然这是一个更高级的,最终用户的例子。 一般来说:这对PC内部,SO,驱动程序,编码系统是有用的。
另一个例子:分析float
。
struct _FP32 { unsigned int sign:1; unsigned int exponent:8; unsigned int mantissa:23; } union FP32_t { _FP32 parts; float number; }
(免责声明:不知道这是应用的文件名称/types的名称,但在C这是在一个头声明;不知道怎么可以这样做的64位flaots,因为尾数必须有52位和 – 在32位目标中有32位)。
结论:正如这个概念和这些例子所显示的,这是一个很less使用的function,因为它主要是为了内部目的,而不是日常的软件。
位字段可用于节省存储空间(但为此目的使用位字段很less见)。 它用于存在内存约束的地方。 例如)在embedded式系统中进行编程。
但是这只能在极其需要的情况下使用。
因为我们不能拥有一个位字段的地址。 所以解决运营商&不能与他们一起使用。
为了回答这个问题的其他部分,没有人回答:
Ints不是短裤
使用整数而不是短裤的原因是,在大多数情况下,这样做不会节省空间。
现代计算机具有32位或64位体系结构,即使使用较短的存储types,也需要32位或64位。
较小的types仅用于保存内存(如果可以将它们打包在一起)(例如,较短的数组可能会使用比int数组更less的内存,因为可以将数组打包在一起更紧密)。 对于大多数使用位域的情况,情况并非如此。
其他用途
位域最常用于标志,但还有其他的用途。 举例来说,一种代表国际象棋algorithm中使用的国际象棋棋盘的方法是使用一个64位的整数来表示棋盘(8 * 8像素)并在该整数中设置标志以给出所有白棋的位置。 另一个整数显示所有的黑棋子等
回答最初的问题»根据Brian Hook(ISBN 1-59327-056-9)的书“Write Portable Code”(ISBN 1-59327-056-9),我读了德语版ISBN 3-937514-19 -8)和个人经验:
切勿使用C语言的位字成语,而是自己去做。
很多实现细节都是编译器特定的,尤其是与联合组合在一起,并且不能保证不同的编译器和不同的字节顺序。 如果只有一个很小的机会,你的代码必须是可移植的,并且将被编译用于不同的体系结构和/或不同的编译器,不要使用它。
我们有这样的情况,当把一些专有的编译器的代码从小端的微控制器移植到另外一个带有GCC的大端微控制器的时候,并不好玩。 : – /
这是我如何使用标志(主机字节顺序;-))从那时起:
# define SOME_FLAG (1 << 0) # define SOME_OTHER_FLAG (1 << 1) # define AND_ANOTHER_FLAG (1 << 2) /* test flag */ if ( someint & SOME_FLAG ) { /* do this */ } /* set flag */ someint |= SOME_FLAG; /* clear flag */ someint &= ~SOME_FLAG;
然后不需要inttypes和一些位域结构的联合。 如果你阅读了大量的embedded式代码,那么testing,设置和清除模式就会变得很普遍,而且你很容易在代码中发现它们。
您可以使用它们来扩展包装的无符号types的数量。 普通的你只有8,16,32,64 …的权力,但是你可以拥有所有的权力。
struct a { unsigned int b : 3 ; } ; struct aw = { 0 } ; while( 1 ) { printf("%u\n" , w.b++ ) ; getchar() ; }
为了利用内存空间,我们可以使用位域。
据我所知在现实世界编程中,如果我们需要的话,我们可以使用布尔值而不是将其声明为整数,然后使位字段。
如果这也是我们经常使用的价值观,那么我们不仅可以节省空间,而且还可以获得性能,因为我们不需要污染高速caching。 然而,caching也是使用位字段的危险,因为对不同位的并发读取和写入将导致数据竞争,并且更新到完全分离的位可能会用旧值覆盖新的值。
为什么我们使用int? 占用了多less空间?
对于这个问题的一个答案,我没有看到任何其他答案中提到的是,C标准保证支持int。 特别:
位字段的types应该是_Bool,signed int,unsigned int或其他实现定义types的合格或不合格版本。
编译器通常允许附加的位域types,但不是必需的。 如果你真的关心可移植性,int是最好的select。
位域更紧凑,这是一个优势。
但是不要忘记打包结构比普通结构慢。 由于程序员必须定义每个字段使用的位数,所以它们也更难构build。这是一个缺点