位域结构体简介

最近实习接触到一个新的知识点,C/C++的位域结构体。

以下开始摘抄自:here

位段(bit-field)是以位为单位来定义结构体(或联合体)中的成员变量所占的空间。含有位段的结构体(联合体)称为位段结构。采用位段结构既能够节省空间,又方便于操作。

位段的定义格式为:

1
type [var]: digits

其中type只能为int,unsigned int,signed int三种类型(int型能不能表示负数视编译器而定,比如VC中int就默认是signed int,能够表示负数)。位段名称var是可选参数,即可以省略。digits表示该位段所占的二进制位数。

举个例子,你可以这样定义一个位域结构体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// sizeof(A): 4
struct A
{
  uint32_t a: 12;
  uint32_t b: 10;
  uint32_t c: 10;
};

// sizeof(B): 12
struct B
{
  uint32_t a;
  uint32_t b;
  uint32_t c;
};

uint32_t实际上是unsigned int的别名,并且指定了用4个字节存储int类型的数据,而 1byte = 8bits, 4个字节共计32bits,结构体A使用了位域的方式,指定了每个成员所占用的bit数。可以看到,A中三个成员总共占用的比特数为32,也就是4个字节。所以结构体A所占用的空间就是4字节,而B没有使用位域,则3个成员各占4个字节,共计12字节。说白了,位域就是将结构体的成员在比特位上编排的更紧凑,更节省空间。

以下开启一段摘抄:

关于位域结构体有以下几点说明:(以下“位段就是位域”,c.f. ref1)

  1. 位段的类型只能是int,unsigned int,signed int三种类型,不能是char型或者浮点型;
  2. 位段占的二进制位数不能超过该基本类型所能表示的最大位数,比如在VC中int是占4个字节,那么最多只能是32位;
  3. 无名位段不能被访问,但是会占据空间;
  4. 不能对位段进行取地址操作;
  5. 若位段占的二进制位数为0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元(这里的位段存储单元经测试在VC环境下是4个字节)开始存放;
  6. 若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int。
  7. 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。
  8. 位段不能出现数组的形式。

对于位段结构,编译器会自动进行存储空间的优化,主要有这几条原则:

  1. 如果一个位段存储单元能够存储得下位段结构中的所有成员,那么位段结构中的所有成员只能放在一个位段存储单元中,不能放在两个位段存储单元中;如果一个位段存储单元不能容纳下位段结构中的所有成员,那么从剩余的位段从下一个位段存储单元开始存放。(在VC中位段存储单元的大小是4字节).
  2. 如果一个位段结构中只有一个占有0位的无名位段,则只占1或0字节的空间(C语言中是占0字节,而C++中占1字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小;
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    
    #include <iostream>
    
    using namespace std;
    
    // sizeof(A): 4
    struct A {
    uint32_t a: 4;
    uint32_t b: 3;
    uint32_t c: 1;
    };
    
    // sizeof(B): 12
    struct B {
    uint32_t a;
    uint32_t b;
    uint32_t c;
    };
    
    // sizeof(C): 8
    struct C {
    uint32_t a: 1;
    uint32_t : 0;
    uint32_t c: 2;  // 不会和a凑在一起,新开一个字节存
    };
    
    // sizeof(D): 12
    struct D {
    uint32_t a: 1;
    uint32_t: 0;    / 大专栏  位域结构体简介/ 隔断
    uint32_t: 6;    // 开启新的位域存储单元
    uint32_t d: 32; // 前一个位域不够放,开启新的存放单元
    };
    
    // sizeof(E): 4
    // 内存分布简图
    // 0000 0000 0000 0000
    // a--- b--- cd-------
    struct E {
    uint32_t a: 1;
    char b;        // 隔断
    uint32_t c: 1; // 在下一个存储单元
    uint32_t d: 15; // 四个成员刚好占用32bits,即4个字节
    };
    
    template <typename T>
    void Print(const T&)
    {
    std::cout << sizeof(T) << std::endl;
    }
    
    // test
    int main()
    {
    A a;
    B b;
    C c;
    D d;
    E e;
    Print(a); // 4
    Print(b); // 12
    Print(c); // 8
    Print(d); // 12
    Print(e); // 4
    return 0;
    }
    

以下测试用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <cstdio>

using namespace std;

// sizeof: 4
struct TcpMsgHead {
  uint32_t length: 16;
  uint32_t flags: 8;
  int      num: 8;
};

void Print(const TcpMsgHead& msg)
{
  printf("length: %8dn", msg.length);
  printf("flags:  %8dn", msg.flags);
  printf("num:    %8dn", msg.num);
}

// test
int main()
{
  TcpMsgHead head;
  head.length = 0xffff; // 2^16 - 1 = 65535
  head.flags = 0xff;    // 2^8 - 1 = 255
  head.num = 0xff;
  Print(head);
  return 0;
}

上述程序的输出为:

1
2
3
length:    65535
flags:       255
num:          -1

解释一下为什么num成员的值为-1:
首先num设置的比特位为8,而num的类型为int,是有符号的。对于有符号的整数,计算机内部使用补码表示的。
0xff 换成二进制
1111 1111
这正好是-1的补码。

其实,Ycm给出的提示已经很明确了:

Ycm提示这里发生了隐式截断。255被截断成了-1. 如果换成 0x11 也就是二进制的 0001 0001, 则不会发生截断,因为8比特足够描述0x11,符号位是0,表示正数,所以这很符合我们的预期。但是如果再加一个2呢?

可以看到Ycm提示发生截断,
529被截断成了17, 即
0010 0001 0001 被截断成了 0001 0001
也就是说,我结构体定义的时候已经确定了num只有8比特位可以存。高于8比特的数据全都截断。如果最高位是1,则会被转成负数,这可能和你的预期不符。所以一定不要设置超过容量的数据。

Reference

  1. 浅谈C语言中的位段

原文地址:https://www.cnblogs.com/liuzhongrong/p/11874888.html

时间: 2024-10-09 21:19:08

位域结构体简介的相关文章

位域结构体多线程访问出错的问题分析

位域结构体能节省一些内存空间,但是使用不当会产生race conditions,导致程序异常,下面简要分析错误产生的原因和解决方案. 首先定义一个简单的bit field结构体. +struct bit_filed { + unsigned a : 1; + unsigned b : 1; + unsigned c : 1; + unsigned d : 1; + unsigned e : 1; + unsigned f : 1; + unsigned g : 1; + unsigned h :

ios开发中的C语言学习—— 结构体简介

在开发过程中,经常会需要处理一组不同类型的数据,比如学生的个人信息,由姓名.年龄.性别.身高等组成,因为这些数据是由不同数据类型组成的,因此不能用数组表示,对于不同数据类型的一组数据,可以采用结构体来进行存储.当然,对于面向对象的语言来说,最好是用类来表示,但是C语言是面向过程的,因此选择用结构体来表示. 一.结构体的定义 struct 结构体名{ 类型名 成员名1; 类型名 成员名2; ... ... 类型名 成员名n; }; 二.结构体的变量声明 1.先定义结构体类型,再定义变量 代码 //

sockaddr与sockaddr_in结构体简介

struct sockaddr { unsigned  short  sa_family;     /* address family, AF_xxx */char  sa_data[14];                 /* 14 bytes of protocol address */};sa_family是地址家族,一般都是“AF_xxx”的形式.好像通常大多用的是都是AF_INET.sa_data是14字节协议地址.此数据结构用做bind.connect.recvfrom.sendt

struct stat结构体简介

在使用这个结构体和方法时,需要引入: <sys/types.h> <sys/stat.h> struct stat这个结构体是用来描述一个linux系统文件系统中的文件属性的结构. 可以有两种方法来获取一个文件的属性: 1.通过路径: int stat(const char *path, struct stat *struct_stat); int lstat(const char *path,struct stat *struct_stat); 两个函数的第一个参数都是文件的路径

嵌入式 Linux C语言(七)——结构体

嵌入式 Linux C语言(六)--结构体 一.结构体简介 1.结构体定义 结构体定义一般有两种方法较为常用: 第一种方法: struct person{ char *name; unisgned int age; }; 第二种方法: typedef struct person{ char *name; unsigned int age; }Person; person实例声明如下: Person person;//声明一个person对象 Person *ptrPerson = (Person

[C/C++基础] 3.结构体、共用体、枚举

概述: 结构体和数组主要有两点不同,首先结构体可以在一个结构中声明不同的数据类型,其次相同结构的结构体变脸是可以相互赋值的. 共用体(联合体)和结构体都是由多个不同的数据类型成员组成,但在任何同一时刻,共用体值存放了一个被选中的成员.而结构体的所有成员都存在. C++的枚举(enum)工具提供了另外一种可以替代const来创建符号常量的方式,枚举表是枚举常量的集合. 3.1 结构体struct 结构体类型变量的定义一般形式为: struct 结构体类型名{ 类型1 成员名1; 类型2 成员名2;

C语言结构体的字节对齐原则

转载:http://blog.csdn.net/shenbin1430/article/details/4292463 为什么要对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同.一些平台对某些特定类型的数据只能从某些特定地址

结构体,联合体union,枚举,sizeof

结构体中的位字段 有些信息在存储时并不需要占用一个完整的字节,有时只需要占用一个或者几个二进制位,为了节省存储空间并使得处理简便,C语言提供了一种数据结构,成为“位域”或者“位段”. C与C++允许指定占用特定位数的结构成员,字段的类型应为整型或者枚举型 ,接下来是冒号:,然后后面跟一个数字,它指定了使用的位数,且可以使用没有名字的字段来提供间距.每个成员都被称为位字段(bit field).例: 1 struct reg 2 { 3 unsigned int SN:4; 4 unsigned

Foundation框架 ---- 结构体

一.基本定义 (1). 使用方式 :#import  <Foundation / Foundation.h> (2). 常用的结构体介绍及简单使用 : 表示字符串位置和长度 : NSRange(location   length) 表示坐标   NSPoint\CGPoint 表示UI元素的大小 NSSize\CGSize 表示UI元素的位置和大小 NSRect\CGRect (CGPint CGSize) 结构体的定义方式 : stuct Date { int year; int month