转载自https://www.cnblogs.com/xd-elegant/p/4127365.html, 根据理解略有删减

概述

有些信息在存储的时候, 并不需要占用一个完整的字节, 而只需要占几个或者一个二进制位。

例如, 在存放一个开关量的时候, 只有0和1两种状态, 用一位二进制位即可。

为了节省存储空间, 并使处理简便, C语言又提供了一种数据结构, 称为位域或者位段

即把一个字节中的二进制位划分为不同的区域, 并说明每个区域的位数。

结构

位域的定义结构如下

struct 位域结构名
{
位域列表
};
  • 位域结构名:每个位域结构都有一个名称使得程序在运行过程中通过名称来进行操作位域。

  • 位域列表: 类型 位域名:位域长度

定义

位域变量的说明与结构变量说明的方式相同, 可采用先定义后说明, 同时定义说明或者直接说明这三种方式。例如

struct bit_struct {
int a:8;
int b:2;
int c:6;
}data;

说明databit_struct变量, 共占两个字节。其中位域a占8位, 位域b占2位, 位域c占6位

说明

  • 一个位域必须存储在同一个字节中, 不能跨两个字节。如一个字节所剩空间不够存放另一位域时, 应从下一单元起存放该位域。也可以有意使某位域从下一单元开始(使用无名位域)

    struct bit_struct
    {
    unsigned a:4
    unsigned b:5 /*从下一单元开始存放*/
    unsigned c:4
    }
  • 由于位域不允许跨两个字节, 因此位域的长度不能大于一个字节的长度

  • 位域可以无位域名, 这时它只用来作填充或调整位置。无名的位域是不能使用的

struct k
{
int a:1
int :2 /*无位域名, 该2位不能使用*/
int b:3
int c:2
};

使用

#include <iostream>
#include <memory.h>
using namespace std;
struct A
{
int a:5;
int b:3;
};
int main(void)
{
char str[100] = "0134324324afsadfsdlfjlsdjfl";
struct A d;
memcpy(&d, str, sizeof(A));
cout << d.a << endl;
cout << d.b << endl;
return 0;
}

在64位机器上运行结果如下:

➜  Desktop g++ 1.cpp -o 1
➜ Desktop ./1
-16
1

解析:在默认情况下, 为了方便对结构体内元素的访问和管理, 当结构体内的元素长度都小于处理器的位数的时候, 便以结构体里面最长的元素为对其单位, 即结构体的长度一定是最长的数据元素的整数倍;如果有结构体内存长度大于处理器位数的元素, 那么就以处理器的位数为对齐单元。由于是32位处理器, 而且结构体中a和b元素类型均为int(也是4个字节), 所以结构体的A占用内存为4个字节。

例程中定义了位域结构A, 两个个位域为a(占用5位), b(占用3位), 所以a和b总共占用了结构A一个字节(低位的一个字节)

当程序运行到14行时, d内存分配情况:

高位00110100001100110011000100110000低位
‘4’‘3’‘1’‘0’

其中d.ad.b占用d低位一个字节(00110000), d.a : 10000, d.b : 001
d.a内存中二进制表示为10000, 由于d.a为有符号的整型变量, 输出时要对符号位进行扩展, 所以结果为-16(二进制为11111111111111111111111111110000)
d.b内存中二进制表示为001, 由于d.b为有符号的整型变量, 输出时要对符号位进行扩展, 所以结果为1(二进制为00000000000000000000000000000001)

位域对齐

如果结构体中含有位域(bit-field), 那么VC中准则是:

  • 如果相邻位域字段的类型相同, 且其位宽之和小于类型的sizeof大小, 则后面的字段将紧邻前一个字段存储, 直到不能容纳为止;
  • 如果相邻位域字段的类型相同, 但其位宽之和大于类型的sizeof大小, 则后面的字段将从新的存储单元开始, 其偏移量为其类型大小的整数倍;
  • 如果相邻的位域字段的类型不同, 则各编译器的具体实现有差异, VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中), Dev-C++和GCC都采取压缩方式;
  • 系统会先为结构体成员按照对齐方式分配空间和填塞(padding), 然后对变量进行位域操作.