C语言没有类似于Java的“垃圾回收”等高级编程语言特性,也不像python那样无需显示声明类型就能使用变量,因此在很多人看来,C语言有些“低级”。但是C语言的这些“低级”也是C语言的优点——使用C语言开发程序,程序员能够准确知道究竟使用了多少资源,以及哪些资源还在内存里,哪些已经被释放。换句话说,C语言程序具备资源的使用确定性。
因此,C语言特别适合用于一些资源比较匮乏的项目开发中。在这些项目中,以嵌入式项目为代表,一般都需要严格控制内存的使用——使用1个字节(Byte)就能存放的值,绝对不定义2个字节宽度的变量。甚至,一些“抠门”的C语言程序员会将1个字节掰成若干个位(bit)使用。
所以,在C语言程序开发中,常常需要操作某个变量特定的位(bit),这对于C语言来说当然没有任何难度,各种移位操作就能够方便的解决该类需求,例如:
unsignedcharstatus;status
=0x;status=~0x01;上面第二行C语言代码将status的第3个位(bit2)设置为1,第三行C语言代码将status的第1个位(bit0)设置为0。可以看出,借助于位运算,C语言可以比较简单的操作status的指定位。不过,C语言这种操作位的方法有时候看起来不够直观——至少没有直接赋值那么直观。
那C语言有没有更加直观的位操作方法呢?
上面的例子通过移位、以及或与非等操作实现对变量status的位操作,但是看起来却不是那么直观,那么C语言有没有更加直观的位操作方法呢?似乎可以借助C语言的联合体(union)和位域(bitfield)语法,间接的实现位操作,请看下面的C语言代码:
unionconvert{unsignedcharstatus;struct__bits{unsignedcharbit0:1;unsignedcharbit1:1;unsignedcharbit2:1;unsignedcharbit3:1;unsignedcharbit4:1;unsignedcharbit5:1;unsignedcharbit6:1;unsignedcharbit7:1;}v;};
此例中status和bits结构体共享一个字节的内存空间,结构体bits利用C言中的位域语法将一个字节的内存空间拆分成8个位,这种情况下,要读写status的位就非常简单了,请看下面的C语言示例代码:
unionconvertcv;//写status的bit3cv.v.bit3=0;//写status的bit7cv.v.bit7=1;//读status的bit5r=cv.v.bit5;编写main()函数,测试通过位域操作status的位,相关C语言代码如下,请看:
intmain(){unionconvertcv;cv.status=0;cv.v.bit3=1;cv.v.bit1=1;printf(%d\n,cv.status);return0;}
main()函数一开始将status置为0,然后将它的bit1和bit3设置为1,也就相当于将status设置为0x0a,编译并执行这段C语言代码,得到如下输出:
#gcct.c#./a.out10一切与预期一致,这样就实现了以“赋值”的形式,操作C语言中的位,而且看起来比“移位与或非操作”更加直观,所以这样的操作更好了?
事实上,有一些C语言程序员的确这么用,至少我的一些同事喜欢这样的位操作。
警惕“常识陷阱”
1个字节(Byte)等于8个位(bit)似乎已经是程序员间的常识了,很少有人质疑这一点。但是作为C语言程序员,我们常常要在不同的硬件平台上做底层开发,应该明白:1个字节等于8个位只是惯例而已,C标准并没有定义这一点。有些编译器并不遵守这个惯例,例如,在Texas的C55xDSP的平台上,1个字节等于16个位。在这个平台上,各种数据类型占用的位数有些奇怪:
以longlong为例,在该平台上longlong之所以等于40bit,而不是我们常用的64bit,是因为它们的ALU是40bit宽,因此编译器规定longlong为40bit可以降低功耗和提升效率。
另外,就算在1个字节等于8个位的硬件平台上,单个字节的8个位是如何分布的也是没有明确标准的。