何时在C中使用位域?

Yoh*_*oth 64 c

关于"我们为什么需要使用位字段"的问题,在Google上搜索我发现位字段用于标记.现在我很好奇,这是实际使用位域的唯一方法吗?我们需要使用位字段来节省空间吗?

从书中定义位域的方法:

struct {
unsigned int is_keyword : 1; 
unsigned int is_extern : 1; 
unsigned int is_static : 1;
} flags;
Run Code Online (Sandbox Code Playgroud)

为什么我们使用int?占用了多少空间?我很困惑为什么我们使用int,但不是short或小于int的东西.据我所知,内存中只占用了1位,而不是整个unsigned int值.这是对的吗?

rio*_*oki 62

一个相当不错的资源是C中的Bit Fields.

基本原因是减少使用的尺寸.例如,如果你写:

struct {
    unsigned int is_keyword; 
    unsigned int is_extern; 
    unsigned int is_static;
} flags;
Run Code Online (Sandbox Code Playgroud)

您将使用至少3 * sizeof(unsigned int)或12个字节来表示3个小标志,这应该只需要3个位.

所以,如果你写:

struct {
    unsigned int is_keyword : 1; 
    unsigned int is_extern : 1; 
    unsigned int is_static : 1;
} flags;
Run Code Online (Sandbox Code Playgroud)

这占用了与1相同的空间unsigned int,因此占用4个字节.在需要更多空间之前,可以将32个一位字段抛出到结构中.

这有点像经典的家庭酿造位字段:

#define IS_KEYWORD 0x01
#define IS_EXTERN  0x02
#define IS_STATIC  0x04
unsigned int flags;
Run Code Online (Sandbox Code Playgroud)

但是比特字段语法更清晰,比较:

if (flags.is_keyword)
Run Code Online (Sandbox Code Playgroud)

反对:

if (flags & IS_KEYWORD)
Run Code Online (Sandbox Code Playgroud)

并且显然不易出错.

  • 接下来的问题是:*何时*我需要节省空间?几乎从不.除非您处于____ ____有限的环境中,否则请避免使用位字段. (6认同)
  • 很好的答案!在讨论位字段及其在内存中的大小时,应该记住,c ++编译器将按如下方式在内存中分配位字段:_same_类型的几个连续位字段成员将按顺序分配.只要需要分配_new_类型,它就会与下一个逻辑内存块的开头对齐.下一个逻辑块将取决于您的处理器.某些处理器可以与8位边界对齐,而其他处理器只能与16位边界对齐. (2认同)

Eri*_*inn 51

现在我好奇,[标志]实际使用位字段的唯一方法是什么?

不,标志不是使用位字段的唯一方法.它们也可用于存储大于一位的值,尽管标志更常见.例如:

typedef enum {
    NORTH = 0,
    EAST = 1,
    SOUTH = 2,
    WEST = 3
} directionValues;

struct {
    unsigned int alice_dir : 2;
    unsigned int bob_dir : 2;
} directions;
Run Code Online (Sandbox Code Playgroud)

我们需要使用位字段来节省空间吗?

位字段可以节省空间.它们还允许更简单的方法来设置非字节对齐的值.我们可以使用与设置字段相同的语法,而不是按位移位和使用按位运算struct.这提高了可读性.有了bitfield,你就可以写了

directions.bob_dir = SOUTH;
Run Code Online (Sandbox Code Playgroud)

但是,手动执行,您需要编写如下内容:

#define BOB_OFFSET 2
directions &= ~(3<<BOB_OFFSET); // clear Bob's bits
directions |= SOUTH<<BOB_OFFSET;
Run Code Online (Sandbox Code Playgroud)

这种改进的可读性可能比在这里和那里保存几个字节更重要.

为什么我们使用int?占用了多少空间?

整个空间int被占用.我们使用int是因为在很多情况下,它并不重要.如果对于单个值,您使用4个字节而不是1个或2个,则您的用户可能不会注意到.对于某些平台,尺寸的确很重要多了,你可以使用占用空间少(其它数据类型char,short,uint8_t,等).

据我所知,内存中只占用了1位,而不是整个unsigned int值.这是对的吗?

不,这不正确.unsigned int即使您只使用其中的8位,整个将存在.

  • @xuanduc611 抱歉,如果不清楚。该语句是对“据我所知,内存中仅占用 1 位,而不是整个 unsigned int 值。”*“整个 int 的空间被占用”的直接答案,旨在解释为什么 `sizeof(directions) ` 将是 `4`(整个 `unsigned int` 被占用),而不是 `1`(如果使用足够大以包含字段的最小数据类型),甚至是 `0.5`(提问者认为会发生的情况) (2认同)

und*_*gor 24

位域常见的另一个地方是硬件寄存器.如果你有一个32位寄存器,其中每个位都有一定的含义,你可以用位域优雅地描述它.

这样的位域本质上是特定于平台的.在这种情况下,便携性无关紧要.

  • 虽然警告是正确的,但我很少看到使用多个编译器的嵌入式项目.通常你坚持用一个项目. (8认同)
  • 可移植性不仅适用于硬件.相同体系结构的不同编译器可能不同意位字段的排序. (5认同)
  • 如果硬件寄存器位于 IP 块中,并且 IP 块驱动程序在多个体系结构中使用,那么您将拥有多个编译器。也就是说,这并不像人们想象的那么罕见。 (2认同)

The*_*kis 11

我们主要(但不是唯一地)使用位字段用于标志结构 - 字节或单词(或可能更大的东西),其中我们尝试打包微小(通常是2状态)的(通常是相关的)信息.

在这些场景中,使用位字段是因为它们正确地模拟了我们正在解决的问题:我们正在处理的问题实际上不是8位(或16位或24位或32位)数字,而是8个(或16个或24个或32个)相关但不同的信息的集合.

我们使用比特字段解决的问题是紧密地"打包"信息具有可测量的益处和/或"解包"信息没有惩罚的问题.例如,如果您通过8个引脚暴露1个字节,并且每个引脚的位通过它们已经印刷在电路板上的总线,以便它准确地引出它应该的位置,则位域是理想的."打包"数据的好处是它可以一次性发送(如果总线的频率有限并且我们的操作依赖于其执行的频率,这是有用的),并且"解包"数据的代价是不存在(或存在但值得).

另一方面,由于计算机体系结构通常的工作方式,我们不会在正常程序流控制等其他情况下使用位字段来表示布尔值.大多数常见的CPU不喜欢从内存中获取一位 - 他们喜欢获取字节或整数.他们也不喜欢处理位 - 它们的指令通常用于整数,字,内存地址等较大的东西.

因此,当您尝试对位进行操作时,由您或编译器(取决于您正在编写的语言)来编写执行位屏蔽的其他操作并删除除了您实际想要的信息之外的所有内容的结构操作.如果"打包"信息没有任何好处(在大多数情况下,没有),那么使用布局的位字段只会在代码中引入开销和噪声.


Jee*_*tel 7

为什么我们需要使用位字段?

当您想要存储一些可以存储小于字节的数据时,这些数据可以使用位字段在结构中耦合.在嵌入式字中,当任何寄存器的32位世界对不同的字具有不同的含义时,您也可以使用位文件来使它们更具可读性.

我发现位字段用于标志.现在我很好奇,这是实际使用位域的唯一方法吗?

不,这不是唯一的方法.您也可以以其他方式使用它.

我们需要使用位字段来节省空间吗?

是.

据我所知,内存中只占用了1位,而不是整个unsigned int值.这是对的吗?

没有.内存只能占用多个字节.


LeS*_*cky 6

回答原始问题»何时使用C中的位字段?«...根据Brian Hook的书"Write Portable Code"(ISBN 1-59327-056-9,我阅读德文版ISBN 3-937514-19 -8)和个人经历:

永远不要使用C语言的位域习语,而是自己动手.

许多实现细节都是特定于编译器的,特别是与工会结合使用时,并不能保证不同的编译器和不同的endianess.如果您的代码必须是可移植的,并且将针对不同的体系结构和/或使用不同的编译器进行编译,那么请不要使用它.

我们遇到过这种情况,将带有一些专有编译器的小端微控制器的代码移植到另一个带有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;
Run Code Online (Sandbox Code Playgroud)

不需要与int类型和一些bitfield结构的联合.如果您阅读了大量嵌入式代码,那么测试,设置和清除模式将变得很常见,您可以在代码中轻松地发现它们.

  • IMO,如果您正在考虑使用位字段,您可能应该同时考虑字节序。 (3认同)
  • 您能否分享一些可能会因特定编译器而中断或无法在不同架构上工作的实际代码?像“NEVER”这样用笑脸装饰但没有反例的东西听起来像是一个强烈固执己见的神话。 (2认同)

Lui*_*lli 5

一个很好的用法是实现一个块来转换为-and-base64或任何未对齐的数据结构.

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.
Run Code Online (Sandbox Code Playgroud)

这个例子有点天真,因为base64也必须考虑空终止(即一个没有长度的字符串,l使l%3为0).但是作为访问未对齐数据结构的示例.

另一个例子:使用此功能将TCP数据包标头分解为其组件(或您想要讨论的其他网络协议数据包标头),虽然它是一个更高级,更少的最终用户示例.一般来说:这对于PC内部,SO,驱动程序,编码系统很有用.

另一个例子:分析一个float数字.

struct _FP32 {
    unsigned int sign:1;
    unsigned int exponent:8;
    unsigned int mantissa:23;
}

union FP32_t {
    _FP32 parts;
    float number;
}
Run Code Online (Sandbox Code Playgroud)

(免责声明:不知道应用此文件的文件名/类型名称,但在C中,这是在标题中声明的;不知道如何对64位flaots进行此操作,因为尾数必须为52位且 - 在32位目标中有32位).

结论:正如概念和这些示例所示,这是一个很少使用的功能,因为它主要用于内部目的,而不是用于日常软件.

  • 这个答案不是标准的 C。允许编译器以它想要的任何方式打包位字段,你不能依赖它是最低有效的和没有填充的。 (2认同)

roh*_*hit 5

位域可用于节省内存空间(但为此目的使用位域的情况很少见)。它用于存在内存限制的地方。例如)在嵌入式系统中编程时。

但是只有在非常需要时才应该使用它。

因为我们不能有位域的地址。所以地址运算符&不能与它们一起使用。