位段

160 阅读5分钟

位段bit segment

早期C语言中没有该标准,后期才加入

位段出现的契机

操作系统中常常要区分开某些文件/用户等的权限,例如一个文件是否有修改权限、删除权限,又比如一个用户是否有添加、删除、修改、查询文件的权限,操作系统都是需要鉴别的

我们假设,使用宏来代表权限:第几个位置为1,表示有哪个权限

#define ADD 1 // 添加的权限 0001
#define DEL 2 // 删除的权限 0010
#define EDT 4 // 修改的权限 0100
#define QUE 8 // 查询的权限 1000

基于此我们可以通过位运算来完成权限的识别与赋予

int _tmain(int argc,TCHAR argv[])
{
	// 用于表示权限
	// 逻辑或运算 0100 | 0010 = 0110 表示当前具有 修改和删除 权限
	int nPrivileges = EDT | DEL;
	
	// 增加权限
	// 0110 | 0001 = 0111 表示当前具有 修改、删除和添加的权限
	nPrivileges = nProvileges | ADD

	// 删除权限
	// 逻辑与运算 0111 & 1101 = 0101 删除掉 删除的权限
  // 由于逻辑与运算是都为1才是1,有一个为0就为0,所以~DEL则代表删除的一位肯定为0,所以&后,nPrivileges该位也肯定为0
	nPrivileges = nPrivileges & ~DEL

	// 判断是否具有某权限
	// 按位求与判断是否具有添加权限
	// =0代表没有该权限 !=0代表具有该权限
	// 0101 & 0001 由于 & 0001 而和的规矩是都为1才为1,所以结果中前三位一定为 0 , 若不具有该权限则最后一个 & 后也为 0 ,所以结果 =0 代表没有该权限
	if ( nPrivileges & ADD )
	{
		puts("具有添加权限");
	}
	return 0;
}

位段bit segment

为了方便的行驶与上述相同的功能,引入一个新的语法:位段

struct tagPrivileges
{
// : 后面的数字代表长度
	int add : 1; // add长度为1位
	int edt : 1;
	int del : 1;
	int que : 1;
}

此时若再想实现上面的权限赋予、权限增加、权限删除、权限判断,可以这么做:

🔥 起始底层还是通过上述的位运算来完成,只不过这一次是交给编译器来完成
int _tmain(int argc,TCHAR argv[])
{
	
	struct tagPrivileges Pri;
	
	//赋予权限
	Pri.add = 1;
	Pri.del = 1

	//删除权限
	Pri.del = 0

	printf("%x\r\n",Pri.add);
}

此时printf会输出什么呢? 期待结果应为 1

QQ截图20220618160608.png

这显然不是我们期待的结果

整型数的位数拓展

对于上述例子,我们前期在结构体中定义的add只占1位,而输出时为%x输出4字节,所以就势必会发生位数的拓展

有符号数的拓展

规则:有符号数做长度拓展时,高位添符号位

对于有符号位,拓展出的多的部分填符号位,这样就不会改变原数的符号性质,整的依旧为正,负的依旧为负

此时对于Pri.add由于前期我们定义其为有符号整型,所以拓展时会将拓展部份填充符号位,又因为其是一个正数,所以符号位为1,所以高位全部填1

所以最终输出的记过就是 FFFFFFFF

无符号数的拓展

规则:高位全部填0

所以我们定义为:

struct tagPrivileges
{
	unsigned int add : 1;
	unsigned int edt : 1;
	unsigned int del : 1;
	unsigned int que : 1;	
};

即可

QQ截图20220618163625.png

幺蛾子时刻

现在对于标志位的定义有以下要求:

QQQDDEEEEA

struct tagPrivileges
{
	unsigned int add : 1;
	unsigned int edt : 4;
	unsigned int del : 2;
	unsigned int que : 3;
}

int _tmain(int argc,TCHAR argv[])
{
	struct tagPrivileges Pri = {0};
	Pri.add = 6; // add明显放不下6位 6 = 110
	Pri.del = 1;
	
  return 0;
}

此时内存中会是什么样子呢?

QQQDDEEEEA

  • Q 没有定义,所以为 000DDEEEEA

  • D 定义为1,所以为 00001EEEEA

  • E 没有定义,所以为 000010000A

  • A 定义为6,也就是二进制的110此时由于A只占1位,所以我们只取A的最低位也就是 0

    0000100000 对应内存中16进制的就是20H

QQ截图20220618165855.png

无名位段

struct tagPrivileges
{
	unsigned int add : 1; 
	unsigned int edt : 4; 
	unsigned int : 3;    // 这三位没有名字,无法引用,但就占3位
	unsigned int del : 2; 
	unsigned int que : 3; 
}
// QQQDD???EEEEA
🔥 **该位置不会被前后占用**

跨对齐位段

struct tagPrivileges
{
	unsigned int add : 1; 
	unsigned int edt : 4; 
	unsigned int : 0;    
	unsigned int del : 2; 
	unsigned int que : 3; 
}
// QQQDDEEEEA

此时构成跨对齐位段,特点:

此时add、edt、?(int : 0)要一共占满4字节(占满一个对齐值4字节,接下来的东西都下一个对齐值开始放),再开始放del、和que

所以对于 int : 0 的作用就是用0和前面的位数补齐一个对齐值的字节数,再开始存放接下来的位段

所以其形式为:

QQQDD???????????????????????????EEEEA (32 - 5 = 27个?)

0001000000000000000000000000000000001 占 8 字节

示例

定义一个浮点编码

struct IEEE
{
	unsigned int data : 23; // 数据位
	unsigned int exp : 8; // 指数位
	unsigned int sig : 1; // 符号位		
};

int _tmain(int argc,TCHAR argv[])
{
	float f = 3.14f;

	struct IEEE *p = (IEEE *)&f;
	int n1 = p->data; //就可以方便的取出浮点数的各位了
	int n2 = p->exp;
	int n3 = p->sig ;

	return 0;
}

QQ截图20220618172957.png