何时在C中使用位域?

时间:2014-07-24 12:06:49

标签: c

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

从书中定义位字段的方法:

struct {
unsigned int is_keyword : 1; 
unsigned int is_extern : 1; 
unsigned int is_static : 1;
} flags;

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

15 个答案:

答案 0 :(得分:56)

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

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

struct {
    unsigned int is_keyword; 
    unsigned int is_extern; 
    unsigned int is_static;
} flags;

您将使用至少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)

显然不易出错。

答案 1 :(得分:48)

  

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

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

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

struct {
    unsigned int alice_dir : 2;
    unsigned int bob_dir : 2;
} directions;
  

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

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

directions.bob_dir = SOUTH;

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

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

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

  

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

整个int的空间被占用。我们使用int因为在很多情况下,它并不重要。如果对于单个值,您使用4个字节而不是1个或2个,则您的用户可能不会注意到。对于某些平台,大小确实更重要,您可以使用占用更少空间的其他数据类型(charshortuint8_t等。

  

据我所知,内存中只占用1位,但不是整个unsigned int值。这是正确的吗?

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

答案 2 :(得分:19)

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

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

答案 3 :(得分:8)

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

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

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

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

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

答案 4 :(得分:6)

为什么我们需要使用位字段&#39;?

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

我发现位字段用于标记。现在我很好奇,是否实际使用位字段的唯一方法是什么?

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

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

是。

据我所知,内存中只占用1位,但不是整个unsigned int值。这是正确的吗?

NO。内存只能占用多个字节。

答案 5 :(得分:6)

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

永远不要使用C语言的bitfield成语,而是自己动手。

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

我们遇到过这种情况,将带有一些专有编译器的小端微控制器的代码移植到另一个带有GCC的大端微控制器,这并不好玩。 : - /

这是我从那时起使用flags(host byte order ;-))的方式:

# 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;

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

答案 6 :(得分: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.

这个例子有点天真,因为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;
}

(免责声明:不知道应用此文件的文件名/类型名称,但在C中,这是在标题中声明的;不知道如何为64位flaots做到这一点尾数必须有52位,而在32位目标中有32位。

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

答案 7 :(得分:3)

位字段可用于节省存储空间(但为此目的使用位字段很少)。它用于存在内存约束的地方。例如,在嵌入式系统中编程时。

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

因为我们不能拥有位字段的地址。因此地址运算符&amp; 不能与它们一起使用。

答案 8 :(得分:2)

您可以使用它们来扩展包装的无符号类型的数量。普通的你只有8,16,32,64 ......的功率,但是你可以拥有位域的所有功能。

struct a
{
    unsigned int b : 3 ;
} ;

struct a w = { 0 } ;

while( 1 )
{
    printf("%u\n" , w.b++ ) ;
    getchar() ;
}

答案 9 :(得分:2)

要回答问题的部分,没有人回答:

不是短裤

使用整数而不是短路等的原因是,在大多数情况下,这样做不会节省空间。

现代计算机具有32位或64位架构,即使您使用较小的存储类型(如短路),也需要32位或64位。

较小的类型只有在将它们打包在一起时才能用于保存内存(例如,短数组可能比int数组使用更少的内存,因为短路可以在阵列中更紧密地打包在一起)。对于大多数使用位域的情况,情况并非如此。

其他用途

Bitfields最常用于标志,但还有其他用途。例如,表示在许多国际象棋算法中使用的国际象棋棋盘的一种方法是使用64位整数来表示棋盘(8 * 8像素)并在该整数中设置标志以给出所有白棋子的位置。另一个整数显示所有黑色棋子等。

答案 10 :(得分:1)

比特字段更紧凑,这是一个优势。

但不要忘记包装结构比正常结构慢。它们也更难构造,因为程序员必须定义每个字段使用的位数。这是一个缺点

答案 11 :(得分:0)

为了利用存储空间,我们可以使用位字段。

据我所知,在真实世界编程中,如果我们要求我们可以使用布尔值而不是将其声明为整数然后制作位域。

答案 12 :(得分:0)

如果它也是我们经常使用的值,我们不仅可以节省空间,还可以获得性能,因为我们不需要污染缓存。然而,缓存也是使用位字段的危险,因为对不同位的并发读取和写入将导致数据争用,并且更新为完全独立的位可能会用旧值覆盖新值。

答案 13 :(得分:0)

  

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

我在其他任何答案中都没有提到的这个问题的一个答案是C标准保证对int的支持。具体做法是:

  

位字段的类型应为_Bool,signed int,unsigned int或其他实现定义类型的限定或非限定版本。

编译器通常允许其他位字段类型,但不是必需的。如果你真的担心可移植性,那么int是最好的选择。

答案 14 :(得分:0)

在我们的项目中,我们使用它从给定的内存地址中提取页表条目和页目录条目:

union VADDRESS {
    struct {
        ULONG64 BlockOffset : 16;
        ULONG64 PteIndex : 14;
        ULONG64 PdeIndex : 14;
        ULONG64 ReservedMBZ : (64 - (16 + 14 + 14));
    };

    ULONG64 AsULONG64;
};

现在假设,我们有一个地址: union VADDRESS tempAddress; tempAddress.AsULONG64 = 0x1234567887654321;

现在我们可以从这个地址访问PTE和PDE:
cout<