赞
踩
目录
处理一个值中的位的两个C工具:位运算符和位字段
C 在提供高级语言便利的同时,还能在为汇编语言所保留的级别上工作,这使其成为编写设备驱动程序和嵌入式代码的首选语言。
以2为基底表示的数字被称为二进制数(binary number)。
通常,1字节包含8位。C语言用字节(byte)表示储存系统字符集所需的大小,所以C字节可能是8位、9位、16位或其他值。不过,描述存储器芯片和数据传输率中所用的字节指的是8位字节。(计算机界通常用八位组(octet)这个术语特指8位字节)
可以从左往右给这8位分别编号为7~0。在1字节中,编号是7的位被称为高阶位(high-order bit),编号是0的位被称为低阶位(low-order bit)。每 1位的编号对应2的相应指数。
1字节可储存0~255范围内的数字,总共256个值。或者,通过不同的方式解释位组合(bit pattern),程序可以用1字节储存-128~+127范围内的整数,总共还是256个值。例如,通常unsigned char用1字节表示的范围是0~255,而signed char用1字节表示的范围是-128~+127。
如何表示有符号整数取决于硬件,而不是C语言。
表示有符号数最简单的方式是用1位(如,高阶位)储存符号,只剩下7位表示数字本身(假设储存在1字节中)。用这种符号量(sign-magnitude)表示法,10000001表示-1,00000001表示1。因此,其表示范围是-127~+127。这种方法的缺点是有两个0:+0和-0。这很容易混淆,而且用两个位组合来表示一个值也有些浪费。
二进制补码(two’s-complement)方法避免了这个问题,是当今最常用的系统。二进制补码用1字节中的后7位表示0~127,高阶位设置为0。如果高阶位是1,表示的值为负。这两种方法的区别在于如何确定负值。从一个9位组合100000000(256的二进制形式)减去一个负数的位组合,结果是该负值的量。例如,10000001 是-127,11111111 是-1。该方法可以表示-128~+127范围内的数。要得到一个二进制补码数的相反数,最简单的方法是反转每一位(即0变为1,1变为0),然后加1。因为1是00000001,那么-1则是11111110+1,或11111111。
二进制反码(one’s-complement)方法通过反转位组合中的每一位形成一个负数。例如,00000001是1,那么11111110是-1。这种方法也有一个-0:11111111。该方法能表示-127~+127之间的数。
浮点数分两部分储存:二进制小数和二进制指数。
在二进制小数中,使用2的幂作为分母,所以二进制小数.101表示为:1/2 + 0/4 + 1/8
许多分数(如,1/3)不能用十进制表示法精确地表示。与此类似,许多分数也不能用二进制表示法准确地表示。实际上,二进制表示法只能精确地表示多个1/2的幂的和。因此,3/4和7/8可以精确地表示为二进制小数,但是1/3和2/5却不能。
为了在计算机中表示一个浮点数,要留出若干位(因系统而异)储存二进制分数,其他位储存指数。
计算机界通常使用八进制记数系统和十六进制记数系统。
每个八进制位对应3个二进制位。
由于没有单独的数(digit,即0~9这样单独一位的数)表示10~15,所以用字母A~F来表示。在C语言中,A~F既可用小写也可用大写。
每个十六进制位都对应一个4位的二进制数(即4个二进制位),那么两个十六进制位恰好对应一个8位字节。第1个十六进制表示前4位,第2个十六进制位表示后4位。因此,十六进制很适合表示字节值。
C有两个操控位的工具。第 1 个工具是一套(6 个)作用于位的按位运算符。第 2 个工具是字段(field)数据形式,用于访问 int中的位。
C 提供按位逻辑运算符和移位运算符。
4个按位逻辑运算符都用于整型数据,包括char。之所以叫作按位(bitwise)运算,是因为这些操作都是针对每一个位进行,不影响它左右两边的位。不要把这些运算符与常规的逻辑运算符(&&、||和!)混淆,常规的逻辑运算符操作的是整个值。
一元运算符~把1变为0,把0变为1。
~(10011010) // 表达式
(01100101) // 结果值
假设val的类型是unsigned char,已被赋值为2。在二进制中,00000010表示2。那么,~val的值是11111101,即253。注意,该运算符不会改变val的值,就像3 * val不会改变val的值一样, val仍然是2。但是,该运算符确实创建了一个可以使用或赋值的新值:
newval = ~val;
printf("%d", ~val);
如果要把val的值改为~val,使用下面这条语句:
val = ~val;
二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1(从真/假方面看,只有当两个位都为真时,结果才为真)。
(10010011) & (00111101) // 表达式
(00010001) // 结果值
C有一个按位与和赋值结合的运算符:&=。
val &= 0377;
val = val & 0377;
二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为1,结果就为1(从真/假方面看,如果两个运算对象中相应的一个位为真或两个位都为真,那么结果为真)。
(10010011) | (00111101) // 表达式
(10111111) // 结果值
C有一个按位或和赋值结合的运算符:|=。
val |= 0377;
val = val | 0377;
二元运算符^逐位比较两个运算对象。对于每个位,如果两个运算对象中相应的位一个为1(但不是两个为1),结果为1(从真/假方面看,如果两个运算对象中相应的一个位为真且不是两个同为1,那么结果为真)。
(10010011) ^ (00111101) // 表达式
(10101110) // 结果值
C有一个按位异或和赋值结合的运算符:^=。
val ^= 0377;
val = val ^ 0377;
按位与运算符常用于掩码(mask)。所谓掩码指的是一些设置为开(1)或关(0)的位组合。
flags = flags & MASK;
假设定义符号常量MASK为2 (即,二进制形式为00000010),只有1号位是1,其他位都是0。把flags中除1号位以外的所有位都设置为0,因为使用按位与运算符(&)任何位与0组合都得0。
可以这样类比:把掩码中的0看作不透明,1看作透明。表达式flags & MASK相当于用掩码覆盖在flags的位组合上,只有MASK为1的位才可见。
用&=运算符可以简化前面的代码,如下所示:
flags &= MASK;
下面这条语句是按位与的一种常见用法:
ch &= 0xff; /* 或者 ch &= 0377; */
前面介绍过oxff的二进制形式是11111111,八进制形式是0377。这个掩码保持ch中最后8位不变,其他位都设置为0。
打开一个值中的特定位,同时保持其他位不变。这种情况可以使用按位或运算符(|)。
以上一节的flags和MASK(只有1号位为1)为例。下面的语句:
flags = flags | MASK;
把flags的1号位设置为1,且其他位不变。因为使用|运算符,任何位与0组合,结果都为本身;任何位与1组合,结果都为1。
flags |= MASK;
这种方法根据MASK中为1的位,把flags中对应的位设置为1,其他位不变。
在不影响其他位的情况下关闭指定的位。
假设要关闭变量flags中的1号位。同样,MASK只有1号位为1(即,打开)。可以这样做:
flags = flags & ~MASK;
由于MASK除1号位为1以外,其他位全为0,所以~MASK除1号位为0以外,其他位全为1。使用&,任何位与1组合都得本身,任何位与0组合都的0。
MASK中为1的位在结果中都被设置(清空)为0。flags中与MASK为0的位相应的位在结果中都未改变。可以使用下面的简化形式:
flags &= ~MASK;
打开已关闭的位,或关闭已打开的位。如果使用^组合一个值和一个掩码,将切换该值与MASK为1的位相对应的位,该值与MASK为0的位相对应的位不变。
flags = flags ^ MASK;
flags ^= MASK;
必须覆盖flags中的其他位,只用1号位和MASK比较:
if ((flags & MASK) == MASK)
puts("Wow!");
由于按位运算符的优先级比==低,所以必须在flags & MASK周围加上圆括号。
移位运算符向左或向右移动位。
左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。
该操作产生了一个新的位值,但是不改变其运算对象。例如,假设stonk为1,那么 stonk<<2为4,但是stonk本身不变,仍为1。可以使用左移赋值运算符(<<=)来更改变量的值。该运算符将变量中的位向左移动其右侧运算对象给定值的位数。
右移运算符(>>)将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢失。对于无符号类型,用0填充空出的位置;对于有符号类型,其结果取决于机器。空出的位置可用0填充,或者用符号位(即,最左端的位)的副本填充。
右移赋值运算符(>>=)将其左侧的变量向右移动指定数量的位数。
移位运算符针对2的幂提供快速有效的乘法和除法:
number << n number乘以2的n次幂
number >> n 如果number为非负,则用number除以2的n次幂
位运算符还可用于从较大单元中提取一些位。例如,假设用一个unsigned long类型的值表示颜色值,低阶位字节储存红色的强度,下一个字节储存绿色的强度,第 3 个字节储存蓝色的强度。随后你希望把每种颜色的强度分别储存在3个不同的unsigned char类型的变量中。那么,可以使用下面的语句:
#define BYTE_MASK 0xff
unsigned long color = 0x002a162f;
unsigned char blue, green, red;
red = color & BYTE_MASK;
green = (color >> 8) & BYTE_MASK;
blue = (color >> 16) & BYTE_MASK;
把数字转换为二进制形式的程序。
- /* binbit.c -- 使用位操作显示二进制 */
- #include <stdio.h>
- #include <limits.h> // 提供 CHAR_BIT 的定义,CHAR_BIT 表示每字节的位数
- char *itobs(int, char *);
- void show_bstr(const char *);
- int main(void)
- {
- char bin_str[CHAR_BIT * sizeof(int) + 1];
- int number;
- puts("Enter integers and see them in binary.");
- puts("Non-numeric input terminates program.");
- while (scanf("%d", &number) == 1)
- {
- itobs(number, bin_str);
- printf("%d is ", number);
- show_bstr(bin_str);
- putchar('\n');
- }
- puts("Bye!");
- return 0;
- }
- char *itobs(int n, char *ps)
- {
- int i;
- const static int size = CHAR_BIT * sizeof(int);
- for (i = size - 1; i >= 0; i--, n >>= 1)
- ps[i] = (01 & n) + '0';
- ps[size] = '\0';
- return ps;
- }
- /*4位一组显示二进制字符串 */
- void show_bstr(const char *str)
- {
- int i = 0;
- while (str[i]) /* 不是一个空字符 */
- {
- putchar(str[i]);
- if (++i % 4 == 0 && str[i])
- putchar(' ');
- }
- }
运行结果:
Enter integers and see them in binary.
Non-numeric input terminates program.
2023
2023 is 0000 0000 0000 0000 0000 0111 1110 0111
1994
1994 is 0000 0000 0000 0000 0000 0111 1100 1010
q
Bye!
01是一个八进制形式的掩码,该掩码除0号位是1之外,其他所有位都为0。用1 & n或01 & n都可以。我们用八进制1而不是十进制1,只是为了更接近计算机的表达方式。
对数组而言,需要的是字符'0'或字符'1'。该值加上'0'即可完成这种转换(假设按顺序编码的数字,如 ASCII)。
编写的函数用于切换一个值中的后 n位,待处理值和 n 都是函数的参数。
~运算符切换一个字节的所有位,而不是选定的少数位。但是,^运算符(按位异或)可用于切换单个位。假设创建了一个掩码,把后n位设置为1,其余位设置为0。然后使用^组合掩码和待切换的值便可切换该值的最后n位,而且其他位不变。方法如下:
int invert_end(int num, int bits)
{
int mask = 0;
int bitval = 1;
while (bits–– > 0)
{
mask |= bitval;
bitval <<= 1;
}
return num ^ mask;
}
- /* invert4.c -- 使用位操作显示二进制 */
- #include <stdio.h>
- #include <limits.h>
- char * itobs(int, char *);
- void show_bstr(const char *);
- int invert_end(int num, int bits);
- int main(void)
- {
- char bin_str[CHAR_BIT * sizeof(int) + 1];
- int number;
- puts("Enter integers and see them in binary.");
- puts("Non-numeric input terminates program.");
- while (scanf("%d", &number) == 1)
- {
- itobs(number, bin_str);
- printf("%d is\n", number);
- show_bstr(bin_str);
- putchar('\n');
- number = invert_end(number, 4);
- printf("Inverting the last 4 bits gives\n");
- show_bstr(itobs(number, bin_str));
- putchar('\n');
- }
- puts("Bye!");
- return 0;
- }
- char *itobs(int n, char *ps)
- {
- int i;
- const static int size = CHAR_BIT * sizeof(int);
- for (i = size - 1; i >= 0; i--, n >>= 1)
- ps[i] = (01 & n) + '0';
- ps[size] = '\0';
- return ps;
- }
- /* 以4位为一组,显示二进制字符串 */
- void show_bstr(const char *str)
- {
- int i = 0;
- while (str[i]) /* 不是空字符 */
- {
- putchar(str[i]);
- if (++i % 4 == 0 && str[i])
- putchar(' ');
- }
- }
- int invert_end(int num, int bits)
- {
- int mask = 0;
- int bitval = 1;
- while (bits-- > 0)
- {
- mask |= bitval;
- bitval <<= 1;
- }
- return num ^ mask;
- }
运行结果:
Enter integers and see them in binary.
Non-numeric input terminates program.
2023
2023 is
0000 0000 0000 0000 0000 0111 1110 0111
Inverting the last 4 bits gives
0000 0000 0000 0000 0000 0111 1110 1000
1994
1994 is
0000 0000 0000 0000 0000 0111 1100 1010
Inverting the last 4 bits gives
0000 0000 0000 0000 0000 0111 1100 0101
q
Bye!
操控位的第2种方法是位字段(bit field)。位字段是一个signed int或unsigned int类型变量中的一组相邻的位(C99和C11新增了_Bool类型的位字段)。位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了一个4个1位的字段:
struct {
unsigned int autfd : 1;
unsigned int bldfc : 1;
unsigned int undln : 1;
unsigned int itals : 1;
} prnt;
可以通过普通的结构成员运算符(.)单独给这些字段赋值:
prnt.itals = 0;
prnt.undln = 1;
带有位字段的结构提供一种记录设置的方便途径。许多设置(如,字体的粗体或斜体)就是简单的二选一。例如,开或关、真或假。如果只需要使用 1 位,就不需要使用整个变量。内含位字段的结构允许在一个存储单元中储存多个设置。有时,某些设置也有多个选择,因此需要多位来表示。字段不限制 1 位大小。
struct {
unsigned int code1 : 2;
unsigned int code2 : 2;
unsigned int code3 : 8;
} prcode;
以上代码创建了两个2位的字段和一个8位的字段。可以这样赋值:
prcode.code1 = 0;
prcode.code2 = 3;
prcode.code3 = 102;
如果声明的总位数超过了一个unsigned int类型的大小会怎样?会用到下一个unsigned int类型的存储位置。一个字段不允许跨越两个unsigned int之间的边界。编译器会自动移动跨界的字段,保持unsignedint的边界对齐。一旦发生这种情况,第1个unsigned int中会留下一个未命名的“洞”。可以用未命名的字段宽度“填充”未命名的“洞”。使用一个宽度为0的未命名字段迫使下一个字段与下一个整数对齐:
struct {
unsigned int field1 : 1 ;
unsigned int : 2 ;
unsigned int field2 : 1 ;
unsigned int : 0 ;
unsigned int field3 : 1 ;
} stuff;
这里,在stuff.field1和stuff.field2之间,有一个2位的空隙;stuff.field3将储存在下一个unsigned int中。
字段储存在一个int中的顺序取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。
通常,把位字段作为一种更紧凑储存数据的方式。例如,假设要在屏幕上表示一个方框的属性。为简化问题,我们假设方框具有如下属性:
可以使用单独的变量或全长(full-sized)结构成员来表示每个属性,但是这样做有些浪费位。例如,只需1位即可表示方框是透明还是不透明;只需1位即可表示边框是显示还是隐藏。8种颜色可以用3位单元的8个可能的值来表示,而3种边框样式也只需2位单元即可表示。总共10位就足够表示方框的5个属性设置。
对于颜色的表示,只打开一位即可表示三原色之一。其他颜色用三原色的组合来表示。
- /* fields.c -- 定义并使用字段 */
- #include <stdio.h>
- #include <stdbool.h> // C99定义了bool、true、false/* 线的样式 */
- #define SOLID 0
- #define DOTTED 1
- #define DASHED 2
- /* 三原色 */
- #define BLUE 4
- #define GREEN 2
- #define RED 1
- /* 混合色 */
- #define BLACK 0
- #define YELLOW (RED | GREEN)
- #define MAGENTA (RED | BLUE)
- #define CYAN (GREEN | BLUE)
- #define WHITE (RED | GREEN | BLUE)
- const char *colors[8] = {"black", "red", "green", "yellow",
- "blue", "magenta", "cyan", "white"};
- struct box_props
- {
- bool opaque : 1; // 或者 unsigned int (C99以前)
- unsigned int fill_color : 3;
- unsigned int : 4;
- bool show_border : 1; // 或者 unsigned int (C99以前)
- unsigned int border_color : 3;
- unsigned int border_style : 2;
- unsigned int : 2;
- };
- void show_settings(const struct box_props *pb);
- int main(void)
- {
- /* 创建并初始化 box_props 结构 */
- struct box_props box = {true, YELLOW, true, GREEN, DASHED};
- printf("Original box settings:\n");
- show_settings(&box);
- box.opaque = false;
- box.fill_color = WHITE;
- box.border_color = MAGENTA;
- box.border_style = SOLID;
- printf("\nModified box settings:\n");
- show_settings(&box);
- return 0;
- }
- void show_settings(const struct box_props *pb)
- {
- printf("Box is %s.\n",
- pb->opaque == true ? "opaque" : "transparent");
- printf("The fill color is %s.\n", colors[pb->fill_color]);
- printf("Border %s.\n",
- pb->show_border == true ? "shown" : "not shown");
- printf("The border color is %s.\n", colors[pb->border_color]);
- printf("The border style is ");
- switch (pb->border_style)
- {
- case SOLID:
- printf("solid.\n");
- break;
- case DOTTED:
- printf("dotted.\n");
- break;
- case DASHED:
- printf("dashed.\n");
- break;
- default:
- printf("unknown type.\n");
- }
- }
运行结果:
Original box settings:
Box is opaque.
The fill color is yellow.
Border shown.
The border color is green.
The border style is dashed.
Modified box settings:
Box is transparent.
The fill color is white.
Border shown.
The border color is magenta.
The border style is solid.
位字段和按位运算符是两种可替换的方法,用哪种方法都可以。可以通过一个联合把结构方法和位方法放在一起。假定声明了struct box_props 类型,然后这样声明联合:
union Views /* 把数据看作结构或unsigned short类型的变量 */
{
struct box_props st_view;
unsigned short us_view;
};
结构的哪一个位字段与unsigned short中的哪一位对应取决于实现和硬件。(为简化起见,下图以16位单元演示了这种情况)
- /* dualview.c -- 位字段和按位运算符 */
- #include <stdio.h>
- #include <stdbool.h>
- #include <limits.h>
- /* 位字段符号常量 */
- /* 边框线样式 */
- #define SOLID 0
- #define DOTTED 1
- #define DASHED 2
- /* 三原色 */
- #define BLUE 4
- #define GREEN 2
- #define RED 1
- /* 混合颜色 */
- #define BLACK 0
- #define YELLOW (RED | GREEN)
- #define MAGENTA (RED | BLUE)
- #define CYAN (GREEN | BLUE)
- #define WHITE (RED | GREEN | BLUE)
- /* 按位方法中用到的符号常量 */
- #define OPAQUE 0x1
- #define FILL_BLUE 0x8
- #define FILL_GREEN 0x4
- #define FILL_RED 0x2
- #define FILL_MASK 0xE
- #define BORDER 0x100
- #define BORDER_BLUE 0x800
- #define BORDER_GREEN 0x400
- #define BORDER_RED 0x200
- #define BORDER_MASK 0xE00
- #define B_SOLID 0
- #define B_DOTTED 0x1000
- #define B_DASHED 0x2000
- #define STYLE_MASK 0x3000
- const char *colors[8] = {"black", "red", "green", "yellow", "blue",
- "magenta","cyan", "white"};
- struct box_props
- {
- unsigned int opaque : 1;
- unsigned int fill_color : 3;
- unsigned int : 4;
- unsigned int show_border : 1;
- unsigned int border_color : 3;
- unsigned int border_style : 2;
- unsigned int : 2;
- };
- union Views /* 把数据看作结构或unsigned short类型的变量 */
- {
- struct box_props st_view;
- unsigned short us_view;
- };
- void show_settings(const struct box_props *pb);
- void show_settings1(unsigned short);
- char *itobs(int n, char *ps);
- int main(void)
- { /* 创建Views联合,并初始化initialize struct box view */
-
-
- union Views box = {1, YELLOW, 1, GREEN, DASHED};
- char bin_str[8 * sizeof(unsigned int) + 1];
- printf("Original box settings:\n");
- show_settings(&box.st_view);
- printf("\nBox settings using unsigned int view:\n");
- show_settings1(box.us_view);
- printf("bits are %s\n",itobs(box.us_view, bin_str));
- box.us_view &= ~FILL_MASK; /* 把表示填充色的位清0 */
- box.us_view |= (FILL_BLUE | FILL_GREEN); /* 重置填充色*/
- box.us_view ^= OPAQUE; /* 切换是否透明的位 */
- //box.us_view |= BORDER_RED; /* 错误的方法*/
- box.us_view &= ~STYLE_MASK; /* 把样式的位清0 */
- box.us_view |= B_DOTTED; /* 把样式设置为点 */
- printf("\nModified box settings:\n");
- show_settings(&box.st_view);
- printf("\nBox settings using unsigned int view:\n");
- show_settings1(box.us_view);
- printf("bits are %s\n",
- itobs(box.us_view, bin_str));
- return 0;
- }
- void show_settings(const struct box_props *pb)
- {
- printf("Box is %s.\n",
- pb->opaque == true ? "opaque" : "transparent");
- printf("The fill color is %s.\n", colors[pb->fill_color]);
- printf("Border %s.\n",
- pb->show_border == true ? "shown" : "not shown");
- printf("The border color is %s.\n", colors[pb->border_color]);
- printf("The border style is ");
- switch (pb->border_style)
- {
- case SOLID:
- printf("solid.\n");
- break;
- case DOTTED:
- printf("dotted.\n");
- break;
- case DASHED:
- printf("dashed.\n");
- break;
- default:
- printf("unknown type.\n");
- }
- }
- void show_settings1(unsigned short us)
- {
- printf("box is %s.\n",
- (us & OPAQUE) == OPAQUE ? "opaque" : "transparent");
- printf("The fill color is %s.\n",
- colors[(us >> 1) & 07]);
- printf("Border %s.\n",
- (us & BORDER) == BORDER ? "shown" : "not shown");
- printf("The border style is ");
- switch (us & STYLE_MASK)
- {
- case B_SOLID:
- printf("solid.\n");
- break;
- case B_DOTTED:
- printf("dotted.\n");
- break;
- case B_DASHED:
- printf("dashed.\n");
- break;
- default:
- printf("unknown type.\n");
- }
- printf("The border color is %s.\n",
- colors[(us >> 9) & 07]);
- }
- char *itobs(int n, char *ps)
- {
- int i;
- const static int size = CHAR_BIT * sizeof(int);
- for (i = size - 1; i >= 0; i--, n >>= 1)
- ps[i] = (01 & n) + '0';
- ps[size] = '\0';
- return ps;
- }
运行结果:
Original box settings:
Box is opaque.
The fill color is yellow.
Border shown.
The border color is green.
The border style is dashed.
Box settings using unsigned int view:
box is opaque.
The fill color is yellow.
Border shown.
The border style is dashed.
The border color is green.
bits are 00000000000000000010010100000111
Modified box settings:
Box is transparent.
The fill color is cyan.
Border shown.
The border color is green.
The border style is dotted.
Box settings using unsigned int view:
box is transparent.
The fill color is cyan.
Border shown.
The border style is dotted.
The border color is green.
bits are 00000000000000000001010100001100
这里,0x8是3号位为1时的值,0x800是11号位为1时的值。可以用下面的#define分别替换上面的#define:
#define FILL_BLUE 1<<3
#define BORDER_BLUE 1<<11
可以使用枚举代替#defined创建符号常量。
按位运算符改变设置更加复杂。例如,要设置填充色为青色。只打开蓝色位和绿色位是不够的:
box.us_view |= (FILL_BLUE | FILL_GREEN); /* 重置填充色 */
问题是该颜色还依赖于红色位的设置。解决这个问题最简单的方法是在设置新值前关闭所有的颜色位。因此,程序中使用了下面两行代码:
box.us_view &= ~FILL_MASK; /* 把表示填充色的位清0 */
box.us_view |= (FILL_BLUE | FILL_GREEN); /* 重置填充色 */
注意:位字段和位的位置之间的相互对应因实现而异。
另外,书中把opaque,opaque都声明为bool类型,但在我的环境里这样会导致初始化不起作用,所以都改成了unsigned int。
struct box_props
{
unsigned int opaque : 1;
unsigned int fill_color : 3;
unsigned int : 4;
unsigned int opaque : 1;
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
对齐指的是如何安排对象在内存中的位置。
_Alignof运算符给出一个类型的对齐要求,在关键字_Alignof后面的圆括号中写上类型名即可:
size_t d_align = _Alignof(float);
假设d_align的值是4,意思是float类型对象的对齐要求是4。也就是说,4是储存该类型值相邻地址的字节数。一般而言,对齐值都应该是2的非负整数次幂。较大的对齐值被称为stricter或者stronger,较小的对齐值被称为weaker。
可以使用_Alignas 说明符指定一个变量或类型的对齐值。但是,不应该要求该值小于基本对齐值。该说明符用作声明的一部分,说明符后面的圆括号内包含对齐值或类型:
_Alignas(double) char c1;
_Alignas(8) char c2;
unsigned char _Alignas(long double) c_arr[sizeof(long double)];
注意:无论_Alignas(type)说明符在类型说明符的前面还是后面,GCC 4.7.3都能识别,后来Clang 3.3 版本也支持了这两种顺序。
- // align.c -- 使用 _Alignof 和 _Alignas (C11)
- #include <stdio.h>
- int main(void)
- {
- double dx;
- char ca;
- char cx;
- double dz;
- char cb;
- char _Alignas(double) cz;
- printf("char alignment: %zd\n", _Alignof(char));
- printf("double alignment: %zd\n", _Alignof(double));
- printf("&dx: %p\n", &dx);
- printf("&ca: %p\n", &ca);
- printf("&cx: %p\n", &cx);
- printf("&dz: %p\n", &dz);
- printf("&cb: %p\n", &cb);
- printf("&cz: %p\n", &cz);
- return 0;
- }
运行结果:
char alignment: 1
double alignment: 8
&dx: 00000000005FFE98
&ca: 00000000005FFE97
&cx: 00000000005FFE96
&dz: 00000000005FFE88
&cb: 00000000005FFE87
&cz: 00000000005FFE80
在我们的系统中,double的对齐值是8,这意味着地址的类型对齐可以被8整除。以0或8结尾的十六进制地址可被8整除。因为char的对齐值是1,对于普通的char类型变量,编译器可以使用任何地址。
在程序中包含 stdalign.h 头文件后,就可以把 alignas 和 alignof 分别作为_Alignas 和_Alignof的别名。这样做可以与C++关键字匹配。
C11在stdlib.h库还添加了一个新的内存分配函数,用于对齐动态分配的内存。该函数的原型如下:
void *aligned_alloc(size_t alignment, size_t size);
第1个参数代表指定的对齐,第2个参数是所需的字节数,其值应是第1个参数的倍数。与其他内存分配函数一样,要使用free()函数释放之前分配的内存。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。