C语言细节 位操作

在C语言中,可以单独操控变量中的位(Bit).这在某些情况下是必需的,如向硬件设备发送信息来控制这些设备,其中每个位都有特定的含义.许多压缩和加密操
作也是直接处理单独的位.C语言在提供高级语言便利的同时,还可以直接处理单独的位(多数高级语言不会处理该级别的细节),这使其成为编写设备驱动程序和
嵌入式代码的首选语言

一.进制,位,字节

略 可以去看高中课本(我记得讲了)

补充:
①C语言用"字节"(Byte)表示存储系统字符集所需的大小,所以C字节可能是8/9/16位或其他值.不过描述存储器芯片和数据传输率中所用的字节指的是8位字节.
在计算机界通常称8位字节为"八位组"(Octet).以下均假定C字节为8位
②对应的基底的次数较高的位称为"高阶位"(High-Order Bit);对应的基底的次数较低的位称为"低阶位"(Low-Order Bit)

二.按位运算符

以下各运算符均不改变参与运算的对象的值

1.按位逻辑运算符
(1)语法:

4个按位逻辑运算符都用于int(包括char)

按位非:~<i1>
  //即按位取反(0→1.1→0)
按位与:<i1>&<i2>
  //即按位进行与运算(0+0→0,1+0→0,0+1→0,1+1→1)
按位或:<i1>|<i2>
  //即按位进行或运算(0+0→0,1+0→1,0+1→1,1+1→1)
按位异或:<i1>^<i2>
  //即按位进行异或运算(0+0→0,1+0→1,0+1→1,1+1→0)
  //注意:这4个运算符都不会改变<i1>或<i2>的值

//实例:
#include <stdio.h>

int main(void) {
	printf("%d\n",~-1);//结果:0
	printf("%d\n",~1);//结果:-2
	printf("%d\n",~(10));//结果:-11
	printf("%d\n",(10)&(10));//结果:10
	printf("%d\n",(10)|(10));//结果:10
	printf("%d\n",(10)^(10));//结果:0
	printf("%d",(25)^(11));//结果:18
	return 0;
}

//######################################################################################################################3种按位逻辑运算符都有相应的赋值运算符:
<i1>&=<i2>相当于<i1>=<i1>&<i2>
<i1>|=<i2>相当于<i1>=<i1>|<i2>
<i1>^=<i2>相当于<i1>=<i1>^<i2>

(2)用法:

//掩码(Mask):
掩码指的是一些设置为01的位组合.1个量A和掩码使用&运算符后,对应于掩码中0的位置全部
为0,对应于1的位置由A决定:
int flag=150;
int mask=2;
printf("%d",mask&flag);
或者说,掩码就像1排窗户,0的位置不透明,1的位置透明

在这里插入图片描述

//打开位(设置位):
有时需要打开1个值中的特定位(即将该位设为1).这可以通过对2个量A/B使用|运算符实现:
int flag=15;
int open=182;
printf("%d",open|flag);//结果:191
上述代码把flag的第1/3/4/6/7个位打开,其他位保持不变

在这里插入图片描述

//关闭位(清空位):
和打开位类似,有时需要关闭1个值中的特定位(即将该位设为0).这可以通过使用&,~运算符实现:
int flag=15;
int open=182;
printf("%d",open&~flag);//结果:176
上述代码把flag的第1/3/4/6/7个位关闭,其他位保持不变

在这里插入图片描述

//切换位:
有时需要在打开某些位的同时关闭另一些位.这可以通过对2个量A/B使用^运算符实现:
int flag=15;
int mask=182;
flag^=mask;
printf("%d",flag);//结果:185
上述代码切换了flag的第1/3/4/6/7个位,其他位保持不变

在这里插入图片描述

//检查位的值:
int flag=15;
int mask=128;
flag|=mask;//切换flag的第1位
if ((flag&mask)==mask) {//如果成功切换了第1位
	printf("Done");
}

2.移位运算符:

左移n位:<x> << <n>
  <x>=<x> << <n>相当于<x><<=<n>
  //移出左末端位的值将丢失,右侧多出的位将用0填充
右移n位:<x> >> <n>
  <x>=<x> >> <n>相当于<x>>>=<n>
  //移出右末端位的值将丢失;左侧多出的位,对无符号类型,将用0填充;对有符号类型,结果取决于机器(用0填充或用原符号位的副本填充)
  //参数说明:
    x:指定要移动的值;int
    n:指定移动的位数;int

//实例:
int b=4;
b=b<<1;
printf("%d\n",b);//结果:8
b=b>>2;
printf("%d",b);//结果:2

//####################################################################################################################

//用法:
①快速乘/除以2的幂:
int b=-13;
printf("%d\n",b<<4);//结果:-208
printf("%d",b>>3);//结果:-2
return 0;
②从较大的单元中提取位:
#include <stdio.h>
#define MASK 0xff

int main(void) {
	unsigned long color=0x00a162f;//存储某个颜色的RGB值
	unsigned char red,blue,green;
	red=color&MASK;//存储红色的强度
	green=(color>>8)&MASK;//存储绿色的强度
	blue=(color>>16)&MASK;//存储蓝色的强度
	printf("R:%d G:%d B:%d",red,green,blue);//结果:R:47 G:22 B:10
	return 0;
}

三.位字段(Bit Field)

操控位的第2种方法是"位字段".位字段是指signed/unsigned int类型变量中的1组相邻的位(C99/C11中新增了_Bool类型的位字段)

位字段通过1个结构体声明来建立,该声明为每个字段提供标签,并确定该字段的宽度和数据类型.如下面的声明建立了41位的字段:
struct {
    unsigned int autofd:1;
    unsigned int bldfc:1;
    unsigned int undln:1;
    unsigned int itals:1;
} prnt;
prnt包含41位的字段,可通过结构成员运算符(.)为每个字段单独赋值:
prnt.itals=0;
prnt.undln=1;
由于每个字段只占用1位的内存空间,所以只能赋值为01.变量prnt被存储在int大小的内存单元中,但在本例中只使用了其中4.很多设置就是简单的
二选一,这时只需使用1,也就不需要使用整个变量.内含位字段的结构体允许在1个存储单元中存储多个设置.不过,C以int作为位字段结构体的基本单
元,因此,即使只有11位字段,也会占用int大小的内存

某些设置需要多位来表示,可以使用以下代码:
struct {
    unsigned int code1:2;
    unsigned int cpde2:2;
    unsigned int code3:8;
} prcode;
上述代码创建了22位的字段和18位的字段,可以这样赋值:
prcode.code1=0;
prcode.code2=3;
prcode.code3=102;
但要确保所赋的值不超过字段可容纳的范围

如果声明的总位数超过了int类型的大小,则会占用下1int大小的存储单元.1个字段不允许跨越2个存储单元间的边界.如果发生跨界,编译器会自动移动
跨界的字段,使其起始位置与下1个存储单元的起始位置对齐.这时第1个存储单元中就会留下空缺.可以使用宽度为0的未命名字段迫使下1个字段与下1个
存储单元对齐:
struct {
    unsigned int field1:1;
    unsigned int       :2;
    unsigned int field2:1;
    unsigned int       :0;
    unsigned int field3:1;
} stuff;
在stuff.field1与stuff.field2间有12位的空缺;而stuff.field3会被存储在下1个存储单元中

需要注意的是,字段存储在int中的顺序取决于机器;不同机器上2个字段间边界的位置也有区别.因此位字段通常是难以移植的

位字段和按位运算符是2种可替换的方法,通常可以任意选择.不过,使用按位运算符常常更麻烦

四.对齐特性

C11中增加了对齐特性,比用位填充字节更自然,还代表了C在处理硬件相关问题上的能力.这里对齐指的是如何安排对象在内存中的位置.例如,为了效率最
大化,系统可能把double存储在4B内存地址上,却允许把char存储在任意地址上.

//####################################################################################################################

给出1个类型的对齐值:size_t _Alignof(<type>)
  //对齐值为n表示存储该(类型)变量的内存地址的起始位置可以被n整除;通常为2的非负整数次幂
  //较大的对齐值称为stricter或stronger,较小的对齐值成为weaker
  //参数说明:
    type:指定数据类型

//实例:
size_t d_align=_Alignof(float);
printf("%d",d_align);//结果:4

//####################################################################################################################

指定变量的对齐值:_Alignas(<Atype>) <type> <var>;
  //要求:_Alignof(<Atype>)≥_Alignof(<type>)
指定变量的对齐值:_Alignas(<a>) <type> <var>;
  //要求:<a>≥_Alignof(<type>)
  //_Alignas()说明符在类型说明符前后均可(从Clang 3.3和GCC 4.7.3开始)
  //参数说明:
    Atype:指定用于确定对齐值的数据类型
    a:指定对齐值
    type:指定变量本身的数据类型
    var:指定变量名

//实例:
_Alignas(double) char ca='a';
char c='b';
printf("%d,%d\n",_Alignof(double),_Alignof(char));//结果:8,1
printf("%p,%p",&ca,&c);//结果:000000000062FE18,000000000062FE17

//####################################################################################################################

在程序中包含stdalign.h头文件后,即可把alignas和alignof分别作为_Alignas_Alignof的别名,这样可以与C++相兼容

//####################################################################################################################

动态分配并对齐内存:void * aligned_alloc(size_t <alignment>,size_t <size>);
  //注意:也需要使用free()释放
  //参数说明:
    alignment:指定对齐值
    size:指定要分配的字节数
      //注意:应为<alignment>的整数倍
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值