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 编程示例

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

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

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

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

4.1 位字段示例

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

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

可以使用单独的变量或全长(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.

4.2 位字段和按位运算符

位字段和按位运算符是两种可替换的方法,用哪种方法都可以。可以通过一个联合把结构方法和位方法放在一起。假定声明了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;
};

五、对齐特性(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 版本也支持了这两种顺序。

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

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C语言中,"|"和"||"都是条件判断运算符,但它们之间有一些重要的区别。 "|"是按位逻辑或运算符,它对两个操作数进行逐位的或运算。它的结果为真,当任一操作数的对应位为1时。例如,如果a=1和b=2,那么a>0的结果为真,b>1的结果也为真,而b>2的结果为假。因此,如果我们使用if(a>0|b>1),那么条件为真(1),因为逻辑或运算符 "|" 不会对操作数进行短路处理。 "||"是逻辑或运算符,它只要有一个操作数为真,整个条件表达式的结果就为真。它是一种短路运算符,也就是说,如果第一个操作数为真,它就不会再去计算第二个操作数的值。同样以a=1和b=2为例,如果我们使用if(a>0||b>1),那么条件为真(1),因为逻辑或运算符 "||" 在第一个操作数为真时就已经确定整个条件为真,不再需要计算第二个操作数。 因此,总结来说,"|"是按位逻辑或运算符,对操作数的每一位进行或运算,而"||"是逻辑或运算符,只要有一个操作数为真,整个条件表达式就为真,并且在第一个操作数为真时就会短路,不再计算第二个操作数的值。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [必须知道的C语言知识细节:|和||、&和&&区别](https://blog.csdn.net/weixin_35462155/article/details/117188808)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [C语言——| 和 ||——& 和 && 的区别用法](https://blog.csdn.net/liu17234050/article/details/104178651)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HaGoq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值