当前位置:   article > 正文

C | 位操作_常见功能的位操作

常见功能的位操作

目录

一、二进制数、位和字节

1.1 二进制整数

1.2 有符号整数

1.3 二进制浮点数

1.二进制小数

2.浮点数表示法

二、其他进制数

2.1 八进制

 2.2 十六进制

三、C按位运算符

3.1 按位逻辑运算符

1.二进制反码或按位取反:~

2.按位与:&

3.按位或:|

4.按位异或:^

3.2 用法:掩码

3.3 用法:打开位(设置位)

3.4 用法:关闭位(清空位)

3.5 用法:切换位

3.6 用法:检查位的值

3.7 移位运算符

1.左移:<<

2.右移:>>

3.用法:移位运算符

3.8 编程示例

3.9 另一个例子

四、位字段

4.1 位字段示例

4.2 位字段和按位运算符

五、对齐特性(C11)


处理一个值中的位的两个C工具:位运算符位字段

C 在提供高级语言便利的同时,还能在为汇编语言所保留的级别上工作,这使其成为编写设备驱动程序嵌入式代码的首选语言。

一、二进制数、位和字节

以2为基底表示的数字被称为二进制数(binary number)

1.1 二进制整数

通常,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。

1.2 有符号整数

如何表示有符号整数取决于硬件,而不是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之间的数

1.3 二进制浮点数

浮点数分两部分储存:二进制小数二进制指数

1.二进制小数

在二进制小数中,使用2的幂作为分母,所以二进制小数.101表示为:1/2 + 0/4 + 1/8

许多分数(如,1/3)不能用十进制表示法精确地表示。与此类似,许多分数也不能用二进制表示法准确地表示。实际上,二进制表示法只能精确地表示多个1/2的幂的和。因此,3/4和7/8可以精确地表示为二进制小数,但是1/3和2/5却不能。

2.浮点数表示

为了在计算机中表示一个浮点数,要留出若干位(因系统而异)储存二进制分数,其他位储存指数。

二、其他进制数

计算机界通常使用八进制记数系统十六进制记数系统

2.1 八进制

每个八进制位对应3个二进制位。

 2.2 十六进制

由于没有单独的数(digit,即0~9这样单独一位的数)表示10~15,所以用字母A~F来表示。在C语言中,A~F既可用小写也可用大写。

每个十六进制位都对应一个4位的二进制数(即4个二进制位),那么两个十六进制位恰好对应一个8位字节。第1个十六进制表示前4位,第2个十六进制位表示后4位。因此,十六进制很适合表示字节值。

 C有两个操控位的工具。第 1 个工具是一套(6 个)作用于位的按位运算符。第 2 个工具是字段(field)数据形式,用于访问 int中的位。

三、C按位运算符

C 提供按位逻辑运算符移位运算符

3.1 按位逻辑运算符

4个按位逻辑运算符都用于整型数据,包括char。之所以叫作按位(bitwise)运算,是因为这些操作都是针对每一个位进行,不影响它左右两边的位。不要把这些运算符与常规的逻辑运算符(&&、||和!)混淆,常规的逻辑运算符操作的是整个值。

1.二进制反码或按位取反:~

一元运算符~把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;

2.按位与:&

二元运算符&通过逐位比较两个运算对象,生成一个新值。对于每个位,只有两个运算对象中相应的位都为1时,结果才为1(从真/假方面看,只有当两个位都为真时,结果才为真)。

(10010011) & (00111101) // 表达式
(00010001) // 结果值

C有一个按位与和赋值结合的运算符:&=

val &= 0377;
val = val & 0377;

3.按位或:|

二元运算符|,通过逐位比较两个运算对象,生成一个新值。对于每个位,如果两个运算对象中相应的位为1,结果就为1(从真/假方面看,如果两个运算对象中相应的一个位为真或两个位都为真,那么结果为真)。

(10010011) | (00111101) // 表达式
(10111111) // 结果值

C有一个按位或和赋值结合的运算符:|=

val |= 0377;
val = val | 0377;

4.按位异或:^

二元运算符^逐位比较两个运算对象。对于每个位,如果两个运算对象中相应的位一个为1(但不是两个为1),结果为1(从真/假方面看,如果两个运算对象中相应的一个位为真且不是两个同为1,那么结果为真)

(10010011) ^ (00111101) // 表达式
(10101110) // 结果值

C有一个按位异或和赋值结合的运算符:^=

val ^= 0377;
val = val ^ 0377;

3.2 用法:掩码

按位与运算符常用于掩码(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。

3.3 用法:打开位(设置位)

打开一个值中的特定位,同时保持其他位不变。这种情况可以使用按位或运算符(|)

以上一节的flags和MASK(只有1号位为1)为例。下面的语句:

flags = flags | MASK;

把flags的1号位设置为1,且其他位不变。因为使用|运算符,任何位与0组合,结果都为本身;任何位与1组合,结果都为1。

flags |= MASK;

这种方法根据MASK中为1的位,把flags中对应的位设置为1,其他位不变。

3.4 用法:关闭位(清空位)

在不影响其他位的情况下关闭指定的位。

假设要关闭变量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;

3.5 用法:切换位

打开已关闭的位,或关闭已打开的位。如果使用^组合一个值和一个掩码,将切换该值与MASK为1的位相对应的位,该值与MASK为0的位相对应的位不变。

flags = flags ^ MASK;
flags ^= MASK;

3.6 用法:检查位的值

必须覆盖flags中的其他位,只用1号位和MASK比较:

if ((flags & MASK) == MASK)
puts("Wow!");

由于按位运算符的优先级比==低,所以必须在flags & MASK周围加上圆括号。

3.7 移位运算符

移位运算符向左或向右移动位。

1.左移:<<

左移运算符(<<)将其左侧运算对象每一位的值向左移动其右侧运算对象指定的位数。左侧运算对象移出左末端位的值丢失,用0填充空出的位置。

该操作产生了一个新的位值,但是不改变其运算对象。例如,假设stonk为1,那么 stonk<<2为4,但是stonk本身不变,仍为1。可以使用左移赋值运算符(<<=)来更改变量的值。该运算符将变量中的位向左移动其右侧运算对象给定值的位数。

2.右移:>>

右移运算符(>>)将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。左侧运算对象移出右末端位的值丢失。对于无符号类型,用0填充空出的位置;对于有符号类型,其结果取决于机器。空出的位置可用0填充,或者用符号位(即,最左端的位)的副本填充

右移赋值运算符(>>=)将其左侧的变量向右移动指定数量的位数。

3.用法:移位运算符

移位运算符针对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;

3.8 编程示例

把数字转换为二进制形式的程序。

  1. /* binbit.c -- 使用位操作显示二进制 */
  2. #include <stdio.h>
  3. #include <limits.h> // 提供 CHAR_BIT 的定义,CHAR_BIT 表示每字节的位数
  4. char *itobs(int, char *);
  5. void show_bstr(const char *);
  6. int main(void)
  7. {
  8. char bin_str[CHAR_BIT * sizeof(int) + 1];
  9. int number;
  10. puts("Enter integers and see them in binary.");
  11. puts("Non-numeric input terminates program.");
  12. while (scanf("%d", &number) == 1)
  13. {
  14. itobs(number, bin_str);
  15. printf("%d is ", number);
  16. show_bstr(bin_str);
  17. putchar('\n');
  18. }
  19. puts("Bye!");
  20. return 0;
  21. }
  22. char *itobs(int n, char *ps)
  23. {
  24. int i;
  25. const static int size = CHAR_BIT * sizeof(int);
  26. for (i = size - 1; i >= 0; i--, n >>= 1)
  27. ps[i] = (01 & n) + '0';
  28. ps[size] = '\0';
  29. return ps;
  30. }
  31. /*4位一组显示二进制字符串 */
  32. void show_bstr(const char *str)
  33. {
  34. int i = 0;
  35. while (str[i]) /* 不是一个空字符 */
  36. {
  37. putchar(str[i]);
  38. if (++i % 4 == 0 && str[i])
  39. putchar(' ');
  40. }
  41. }

运行结果:

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)。

3.9 另一个例子

编写的函数用于切换一个值中的后 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;
}

  1. /* invert4.c -- 使用位操作显示二进制 */
  2. #include <stdio.h>
  3. #include <limits.h>
  4. char * itobs(int, char *);
  5. void show_bstr(const char *);
  6. int invert_end(int num, int bits);
  7. int main(void)
  8. {
  9. char bin_str[CHAR_BIT * sizeof(int) + 1];
  10. int number;
  11. puts("Enter integers and see them in binary.");
  12. puts("Non-numeric input terminates program.");
  13. while (scanf("%d", &number) == 1)
  14. {
  15. itobs(number, bin_str);
  16. printf("%d is\n", number);
  17. show_bstr(bin_str);
  18. putchar('\n');
  19. number = invert_end(number, 4);
  20. printf("Inverting the last 4 bits gives\n");
  21. show_bstr(itobs(number, bin_str));
  22. putchar('\n');
  23. }
  24. puts("Bye!");
  25. return 0;
  26. }
  27. char *itobs(int n, char *ps)
  28. {
  29. int i;
  30. const static int size = CHAR_BIT * sizeof(int);
  31. for (i = size - 1; i >= 0; i--, n >>= 1)
  32. ps[i] = (01 & n) + '0';
  33. ps[size] = '\0';
  34. return ps;
  35. }
  36. /* 以4位为一组,显示二进制字符串 */
  37. void show_bstr(const char *str)
  38. {
  39. int i = 0;
  40. while (str[i]) /* 不是空字符 */
  41. {
  42. putchar(str[i]);
  43. if (++i % 4 == 0 && str[i])
  44. putchar(' ');
  45. }
  46. }
  47. int invert_end(int num, int bits)
  48. {
  49. int mask = 0;
  50. int bitval = 1;
  51. while (bits-- > 0)
  52. {
  53. mask |= bitval;
  54. bitval <<= 1;
  55. }
  56. return num ^ mask;
  57. }

运行结果:

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中的顺序取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。

4.1 位字段示例

通常,把位字段作为一种更紧凑储存数据的方式。例如,假设要在屏幕上表示一个方框的属性。为简化问题,我们假设方框具有如下属性:

  • 方框是透明的或不透明的;
  • 方框的填充色选自以下调色板:黑色、红色、绿色、黄色、蓝色、紫色、青色或白色;
  • 边框可见或隐藏;
  • 边框颜色与填充色使用相同的调色板;
  • 边框可以使用实线、点线或虚线样式。

可以使用单独的变量或全长(full-sized)结构成员来表示每个属性,但是这样做有些浪费位。例如,只需1位即可表示方框是透明还是不透明;只需1位即可表示边框是显示还是隐藏。8种颜色可以用3位单元的8个可能的值来表示,而3种边框样式也只需2位单元即可表示。总共10位就足够表示方框的5个属性设置。

对于颜色的表示,只打开一位即可表示三原色之一。其他颜色用三原色的组合来表示。

 

  1. /* fields.c -- 定义并使用字段 */
  2. #include <stdio.h>
  3. #include <stdbool.h> // C99定义了bool、true、false/* 线的样式 */
  4. #define SOLID 0
  5. #define DOTTED 1
  6. #define DASHED 2
  7. /* 三原色 */
  8. #define BLUE 4
  9. #define GREEN 2
  10. #define RED 1
  11. /* 混合色 */
  12. #define BLACK 0
  13. #define YELLOW (RED | GREEN)
  14. #define MAGENTA (RED | BLUE)
  15. #define CYAN (GREEN | BLUE)
  16. #define WHITE (RED | GREEN | BLUE)
  17. const char *colors[8] = {"black", "red", "green", "yellow",
  18. "blue", "magenta", "cyan", "white"};
  19. struct box_props
  20. {
  21. bool opaque : 1; // 或者 unsigned int (C99以前)
  22. unsigned int fill_color : 3;
  23. unsigned int : 4;
  24. bool show_border : 1; // 或者 unsigned int (C99以前)
  25. unsigned int border_color : 3;
  26. unsigned int border_style : 2;
  27. unsigned int : 2;
  28. };
  29. void show_settings(const struct box_props *pb);
  30. int main(void)
  31. {
  32. /* 创建并初始化 box_props 结构 */
  33. struct box_props box = {true, YELLOW, true, GREEN, DASHED};
  34. printf("Original box settings:\n");
  35. show_settings(&box);
  36. box.opaque = false;
  37. box.fill_color = WHITE;
  38. box.border_color = MAGENTA;
  39. box.border_style = SOLID;
  40. printf("\nModified box settings:\n");
  41. show_settings(&box);
  42. return 0;
  43. }
  44. void show_settings(const struct box_props *pb)
  45. {
  46. printf("Box is %s.\n",
  47. pb->opaque == true ? "opaque" : "transparent");
  48. printf("The fill color is %s.\n", colors[pb->fill_color]);
  49. printf("Border %s.\n",
  50. pb->show_border == true ? "shown" : "not shown");
  51. printf("The border color is %s.\n", colors[pb->border_color]);
  52. printf("The border style is ");
  53. switch (pb->border_style)
  54. {
  55. case SOLID:
  56. printf("solid.\n");
  57. break;
  58. case DOTTED:
  59. printf("dotted.\n");
  60. break;
  61. case DASHED:
  62. printf("dashed.\n");
  63. break;
  64. default:
  65. printf("unknown type.\n");
  66. }
  67. }

运行结果:

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.

4.2 位字段和按位运算符

位字段和按位运算符是两种可替换的方法,用哪种方法都可以。可以通过一个联合把结构方法和位方法放在一起。假定声明了struct box_props 类型,然后这样声明联合:

union Views /* 把数据看作结构或unsigned short类型的变量 */
{
struct box_props st_view;
unsigned short us_view;
};

结构的哪一个位字段与unsigned short中的哪一位对应取决于实现和硬件。(为简化起见,下图以16位单元演示了这种情况)

 

  1. /* dualview.c -- 位字段和按位运算符 */
  2. #include <stdio.h>
  3. #include <stdbool.h>
  4. #include <limits.h>
  5. /* 位字段符号常量 */
  6. /* 边框线样式 */
  7. #define SOLID 0
  8. #define DOTTED 1
  9. #define DASHED 2
  10. /* 三原色 */
  11. #define BLUE 4
  12. #define GREEN 2
  13. #define RED 1
  14. /* 混合颜色 */
  15. #define BLACK 0
  16. #define YELLOW (RED | GREEN)
  17. #define MAGENTA (RED | BLUE)
  18. #define CYAN (GREEN | BLUE)
  19. #define WHITE (RED | GREEN | BLUE)
  20. /* 按位方法中用到的符号常量 */
  21. #define OPAQUE 0x1
  22. #define FILL_BLUE 0x8
  23. #define FILL_GREEN 0x4
  24. #define FILL_RED 0x2
  25. #define FILL_MASK 0xE
  26. #define BORDER 0x100
  27. #define BORDER_BLUE 0x800
  28. #define BORDER_GREEN 0x400
  29. #define BORDER_RED 0x200
  30. #define BORDER_MASK 0xE00
  31. #define B_SOLID 0
  32. #define B_DOTTED 0x1000
  33. #define B_DASHED 0x2000
  34. #define STYLE_MASK 0x3000
  35. const char *colors[8] = {"black", "red", "green", "yellow", "blue",
  36. "magenta","cyan", "white"};
  37. struct box_props
  38. {
  39. unsigned int opaque : 1;
  40. unsigned int fill_color : 3;
  41. unsigned int : 4;
  42. unsigned int show_border : 1;
  43. unsigned int border_color : 3;
  44. unsigned int border_style : 2;
  45. unsigned int : 2;
  46. };
  47. union Views /* 把数据看作结构或unsigned short类型的变量 */
  48. {
  49. struct box_props st_view;
  50. unsigned short us_view;
  51. };
  52. void show_settings(const struct box_props *pb);
  53. void show_settings1(unsigned short);
  54. char *itobs(int n, char *ps);
  55. int main(void)
  56. { /* 创建Views联合,并初始化initialize struct box view */
  57. union Views box = {1, YELLOW, 1, GREEN, DASHED};
  58. char bin_str[8 * sizeof(unsigned int) + 1];
  59. printf("Original box settings:\n");
  60. show_settings(&box.st_view);
  61. printf("\nBox settings using unsigned int view:\n");
  62. show_settings1(box.us_view);
  63. printf("bits are %s\n",itobs(box.us_view, bin_str));
  64. box.us_view &= ~FILL_MASK; /* 把表示填充色的位清0 */
  65. box.us_view |= (FILL_BLUE | FILL_GREEN); /* 重置填充色*/
  66. box.us_view ^= OPAQUE; /* 切换是否透明的位 */
  67. //box.us_view |= BORDER_RED; /* 错误的方法*/
  68. box.us_view &= ~STYLE_MASK; /* 把样式的位清0 */
  69. box.us_view |= B_DOTTED; /* 把样式设置为点 */
  70. printf("\nModified box settings:\n");
  71. show_settings(&box.st_view);
  72. printf("\nBox settings using unsigned int view:\n");
  73. show_settings1(box.us_view);
  74. printf("bits are %s\n",
  75. itobs(box.us_view, bin_str));
  76. return 0;
  77. }
  78. void show_settings(const struct box_props *pb)
  79. {
  80. printf("Box is %s.\n",
  81. pb->opaque == true ? "opaque" : "transparent");
  82. printf("The fill color is %s.\n", colors[pb->fill_color]);
  83. printf("Border %s.\n",
  84. pb->show_border == true ? "shown" : "not shown");
  85. printf("The border color is %s.\n", colors[pb->border_color]);
  86. printf("The border style is ");
  87. switch (pb->border_style)
  88. {
  89. case SOLID:
  90. printf("solid.\n");
  91. break;
  92. case DOTTED:
  93. printf("dotted.\n");
  94. break;
  95. case DASHED:
  96. printf("dashed.\n");
  97. break;
  98. default:
  99. printf("unknown type.\n");
  100. }
  101. }
  102. void show_settings1(unsigned short us)
  103. {
  104. printf("box is %s.\n",
  105. (us & OPAQUE) == OPAQUE ? "opaque" : "transparent");
  106. printf("The fill color is %s.\n",
  107. colors[(us >> 1) & 07]);
  108. printf("Border %s.\n",
  109. (us & BORDER) == BORDER ? "shown" : "not shown");
  110. printf("The border style is ");
  111. switch (us & STYLE_MASK)
  112. {
  113. case B_SOLID:
  114. printf("solid.\n");
  115. break;
  116. case B_DOTTED:
  117. printf("dotted.\n");
  118. break;
  119. case B_DASHED:
  120. printf("dashed.\n");
  121. break;
  122. default:
  123. printf("unknown type.\n");
  124. }
  125. printf("The border color is %s.\n",
  126. colors[(us >> 9) & 07]);
  127. }
  128. char *itobs(int n, char *ps)
  129. {
  130. int i;
  131. const static int size = CHAR_BIT * sizeof(int);
  132. for (i = size - 1; i >= 0; i--, n >>= 1)
  133. ps[i] = (01 & n) + '0';
  134. ps[size] = '\0';
  135. return ps;
  136. }

运行结果:

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;
};

五、对齐特性(C11)

对齐指的是如何安排对象在内存中的位置。

_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 版本也支持了这两种顺序。

  1. // align.c -- 使用 _Alignof 和 _Alignas (C11)
  2. #include <stdio.h>
  3. int main(void)
  4. {
  5. double dx;
  6. char ca;
  7. char cx;
  8. double dz;
  9. char cb;
  10. char _Alignas(double) cz;
  11. printf("char alignment: %zd\n", _Alignof(char));
  12. printf("double alignment: %zd\n", _Alignof(double));
  13. printf("&dx: %p\n", &dx);
  14. printf("&ca: %p\n", &ca);
  15. printf("&cx: %p\n", &cx);
  16. printf("&dz: %p\n", &dz);
  17. printf("&cb: %p\n", &cb);
  18. printf("&cz: %p\n", &cz);
  19. return 0;
  20. }

运行结果:

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()函数释放之前分配的内存。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/112306
推荐阅读
相关标签
  

闽ICP备14008679号