位域

位域

让人疑惑的C语言位域 - 知乎

Excerpt

在实际的应用中,有些数据的存储只需要几个二进制位,而不需要一个字节或几个字节,比如:电灯接通电源的状态,只有通电和未通电两种状态,用 1 和 0 就可以表示,为了满足这种需求,C 语言中引入了位域的概念 位…


在实际的应用中,有些数据的存储只需要几个二进制位,而不需要一个字节或几个字节,比如:电灯接通电源的状态,只有通电和未通电两种状态,用 1 和 0 就可以表示,为了满足这种需求,C 语言中引入了位域的概念

位域是什么

位域是一种数据结构,可以把数据以二进制位的形式紧凑的存储,它允许程序对此结构的位进行操作

在计算机早期,内存是非常稀缺的,需要尽可能的节省每一个字节,所以,C 语言中就出现了能针对二进制位进行操作的位域

为什么要用位域

位域这种数据结构,可以最大限度的节省存储空间,对于一些非常频繁的操作,需要尽可能的减少操作的数据,比如:在开发网络应用时,数据的序列化和反序列化是很频繁的,如果能减少数据的长度,对提升数据打包效率是很有帮助的

位域的出现,让我们可以用变量名代表某些bit,并通过变量名获取和设置 bit 的值,而不是通过晦涩难理解的位操作来进行,例如:

1
2
3
4
5
6
7
8
9
struct field
{
unsigned char b0 : 3,
b1 : 2,
b2 : 3;
};

struct field bf;
bf.b1 = 3;

通过位域设置中间 2 个bit 的值,只需要设置结构体中 b1 字段值即可,如果使不用位域字段,就需要进行位的 “或” 和 “与” 运算

位域的使用

C 语言中,位域的表示形式如下

1
2
3
4
5
6
7
8
struct bitfield
{
unsigned int b0 : n0,
b1 : n1,
b2 : n2,
...
bn : nk;
};

b0、b1、b2 ... bn 表示位域成员,n0、n1、n2 ... nk 表示成员占用多少个 bit

位域表示的范围通常不能超过其所依附类型所能表示的 bit 数,比如:上面 bitfield 结构体中 位域所依附的类型是 unsigned int, 最大能表示 32 个 bit,也就是说,n0、n1、n2 ... nk 总 bit 数不能超过 32,每个成员超过指定 bit 表示的最大数值时会被截断,具体请看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main()
{
struct fields
{
unsigned short a:7;
unsigned short b:5;
unsigned char c:4;

}fs;

fs.a = 0x8F;
fs.b = 0x1A;
fs.c = 0x19;

printf("%#x, %#x, %#x \n",fs.a, fs.b, fs.c);

}

gcc -g -o bitfield bitfield.c 编译并运行,结果为

1
2
[root@localhost]# ./bitfield                   
0xf, 0x1a, 0x9

字段 a 赋值为 0x8F 对应的二进制为 1000 1111,由于 a 只有 7 个bit,给它赋的值超出了限定的位数,超出部分被丢弃,保留低 7 位,最终结果为 000 1111 ,换成十六进制是 0xf

字段 b 赋值为 0x1A 对应的二进制为 1 1010b 包含 5 个bit,取结果中的低 5 位,最终结果为 1 1010 ,换成十六进制是 0x1a , 输出结果和赋值相同,即没有超出限定 bit 数

字段 c 赋值为 0x19 对应的二进制为 1 1001,由于 c 只有 4 个bit,给它赋的值超出了限定的位数,超出部分被丢弃,保留低 4 位,最终结果为 1001 ,换成十六进制是 0x9

位域的使用有一定的限制,机器最小粒度的寻址单位是字节,我们无法像获得某一个字节的地址一样去获得某个 bit 的地址,下面是一个错误的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main()
{
struct fields
{
unsigned short a:7;
unsigned short b:5;
unsigned char c:4;
}fs;

printf("%p\n",&fs.a);

}

上述代码功能是打印出成员 a 的地址,它无法通过编译,错误如下

1
2
3
4
bitfield.c: 在函数‘main’中:
bitfield.c:11:5: 错误:无法取得位段‘a’的地址
printf("%p\n",&fs.a);
^

位域的存储

C 标准中只允许 unsigned intsigned intint 类型的位域申明,后面又增加了 bool 类型的支持,一些编译器像 gccmsvc等自行加入了一些扩展,使得其他的类型(short、char等)也支持位域

位域的存储跟编译器相关,不同的编译器,存储位域的方式可能不一样,总的来说可以分成下面几类

1、相邻位域成员,它们的类型相同时

如果它们的 bit 数之和小于等于所依附类型的 bit 数,那么,后面的成员紧接着前面的成员存储

如果它们的 bit 数之和大于所依附类型的 bit 数,那么超过的成员会存储到新的存储单元中,新存储单元会偏移成员所依附类型的 sizeof 字节数

以下面的代码为例来说明

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
struct flag
{
unsigned short a:10;
unsigned short b:4;
unsigned short c:2;
}fg;
printf("%d\n", sizeof(fg));
return 0;
}

编译运行,结果如下

1
2
[root@localhost]# ./bitfield                   
2

a 、b、c 字段都是 unsigned short 类型,它们的 bit 数之和为 10 + 4 + 2 = 16, 刚好等于 unsigned short 的 bit 数,所以它们会紧凑的存储,没有任何空隙

如果把 a 的 bit 数改成 11,即 **unsigned short a:11;**,此时,ab 的 bit 数之和为 11 + 4 = 15,没有超过 unsigned short 的 bit 数

如果再加上 c 的 bit 数,结果变成了 17,超过了 unsigned short 的 bit 数,这种情况下,ab 还是会紧凑的存储,而 c 会存储到新的存储单元中,新的存储单元字节数为 sizeof(unsigned short) = 2, 所以此时 sizeof(fg)4

2、相邻位域成员,它们的类型不同时

这种情况跟具体的编译器相关,以下面的代码为例来说明

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
int main()
{
struct flag
{
unsigned short a:10;
unsigned char b:4;
}fg;
printf("%d\n", sizeof(fg));
return 0;
}

上述代码分别用 gcc4.8.5 和 vs2013 进行编译运行,结果如下

gcc的结果

vs2013的结果

可以看到,当相邻位域成员所依附的类型不同时,不同的编译器产生的结果是不一样的

在 gcc 下的运行结果是 2 ,表示 ab 还是紧凑存储的

而在 vs2013 下运行的结果是 4,这说明 ab 完全按照他们所依附的类型来存储,此时位域没有进行压缩存储

3、位域成员之间存在非位域成员时

这种情况 gcc 和 vs2013 都不会进行压缩存储,按照内存对齐的规则来存储

还是以下面的代码为例来说明

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main()
{
struct flag
{
unsigned short a:10;
unsigned int i;
unsigned char b:4;
}fg;
printf("%d\n", sizeof(fg));
return 0;
}

上述代码分别用 gcc4.8.5 和 vs2013 进行编译运行,结果如下

gcc的结果

vs2013的结果

不管在 gcc 还是在 vs2013 下,结果都相同,为了提高访问效率,成员按照 4 字节对齐,所以 sizeof(fg) 结果是 12

现在位域使用得也比较少了,大概有以下几个的原因

1、早期计算机内存很稀缺,在内存的使用上需要精打细算,但是,现代的计算机内存容量有了很大的提升,一般不需要为了节省几个字节而使用内存更加紧凑的位域

2、通过前面的介绍,我们知道结构体中位域的存储是跟编译器相关,这就导致了它的可移植性比较差

匿名位域

位域成员可以不指定名字,只给出成员的数据类型以及占用的 bit 数,称作匿名位域

匿名位域字段只是起填充 bit,调整成员位置的作用,并无实际的意义

因为没有指定成员名字,所以也不能使用

1
2
3
4
5
6
7
struct fields
{

unsigned short a:10,
:6; //匿名位域,不能使用
unsigned short b:3;
};

上面例子中,如果没有匿名位域的话,sizeof(fields) 的结果为 2,加入 6 个 bit 的填充以后,ab 将分开存储, sizeof(fields) 的结果变成了 4

我们还可以通过匿名0长度的位域字段来强制位域存储到下一个存储单元中,例如:

1
2
3
4
5
struct fields
{
unsigned short a:10;
unsigned short b:3;
};

上面的结构体本来可以全部存储到一个 2 字节的存储单元中,如果我们想让 ab 存储到不同的存储单元中,可以在结构体中加入一个匿名的 0 长度的位域字段来实现

1
2
3
4
5
6
struct fields
{
unsigned short a:10;
unsigned short :0;
unsigned short b:3;
};

这样申明后,sizeof(struct fields) 就变成 4

小结

本文讲述了位域的基础,使用以及存储,其中位域的存储跟具体的编译器实现相关,这一点务必要注意,否则版本移植的时候要趟”坑”

内联函数

内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方,对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

静态成员

静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
  • 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。

任务列表语法

任务列表语法

任务列表使您可以创建带有复选框的项目列表。在支持任务列表的Markdown应用程序中,复选框将显示在内容旁边。要创建任务列表,请在任务列表项之前添加破折号-和方括号[ ],并在[ ]前面加上空格。要选择一个复选框,请在方括号[x]之间添加 x 。

1
2
3
- [x] Write the press release
- [ ] Update the website
- [ ] Contact the media

呈现的输出如下所示:

  • Write the press release
  • Update the website
  • Contact the media

初始化Git仓库

初始化Git仓库

$ cd /home
$ mkdir gitrepo
$ chown git:git gitrepo/
$ cd gitrepo

$ git init –bare runoob.git
Initialized empty Git repository in /home/gitrepo/runoob.git/

以上命令Git创建一个空仓库,服务器上的Git仓库通常都以.git结尾。然后,把仓库所属用户改为git:

$ chown -R git:git runoob.git

分割线语法

Markdown分割线语法

要创建分隔线,请在单独一行上使用三个或多个星号 (***)、破折号 (---) 或下划线 (___) ,并且不能包含其他内容。

1
2
3
4
5
***

---

_________________

以上三个分隔线的渲染效果看起来都一样:

# 分隔线(Horizontal Rule)用法最佳实践

为了兼容性,请在分隔线的前后均添加空白行。

✅  Do this ❌  Don’t do this
`Try to put a blank line before…

…and after a horizontal rule.|Without blank lines, this would be a heading.

Don’t do this!` |

删除线语法

删除线语法

您可以通过在单词中心放置一条水平线来删除单词。结果看起来像这样。此功能使您可以指示某些单词是一个错误,要从文档中删除。若要删除单词,请在单词前后使用两个波浪号~~

1
~~世界是平坦的。~~ 我们现在知道世界是圆的。

呈现的输出如下所示:

世界是平坦的。 我们现在知道世界是圆的。

定义列表

定义列表

vs code 无效

一些Markdown处理器允许您创建术语及其对应定义的_定义列表_。要创建定义列表,请在第一行上键入术语。在下一行,键入一个冒号,后跟一个空格和定义。

1
2
3
4
5
6
First Term
: This is the definition of the first term.

Second Term
: This is one definition of the second term.
: This is another definition of the second term.

HTML看起来像这样:

1
2
3
4
5
6
7
<dl>
<dt>First Term</dt>
<dd>This is the definition of the first term.</dd>
<dt>Second Term</dt>
<dd>This is one definition of the second term. </dd>
<dd>This is another definition of the second term.</dd>
</dl>

呈现的输出如下所示:

First Term
This is the definition of the first term.
Second Term
: This is one definition of the second term.
This is another definition of the second term.

引用

Markdown 使用

要创建块引用,请在段落前添加一个 > 符号。

1
> Dorothy followed her through many of the beautiful rooms in her castle.

渲染效果如下所示:

Dorothy followed her through many of the beautiful rooms in her castle.

# 多个段落的块引用

块引用可以包含多个段落。为段落之间的空白行添加一个 > 符号。

1
2
3
> Dorothy followed her through many of the beautiful rooms in her castle.
>
> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.

渲染效果如下:

Dorothy followed her through many of the beautiful rooms in her castle.

The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.

# 嵌套块引用

块引用可以嵌套。在要嵌套的段落前添加一个 >> 符号。

1
2
3
> Dorothy followed her through many of the beautiful rooms in her castle.
>
>> The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.

渲染效果如下:

Dorothy followed her through many of the beautiful rooms in her castle.

The Witch bade her clean the pots and kettles and sweep the floor and keep the fire fed with wood.

# 带有其它元素的块引用

块引用可以包含其他 Markdown 格式的元素。并非所有元素都可以使用,你需要进行实验以查看哪些元素有效。

1
2
3
4
5
6
> #### The quarterly results look great!
>
> - Revenue was off the chart.
> - Profits were higher than ever.
>
> *Everything* is going according to **plan**.

渲染效果如下:

The quarterly results look great!

  • Revenue was off the chart.
  • Profits were higher than ever.

Everything is going according to plan.

强调语法

Markdown强调语法

通过将文本设置为粗体或斜体来强调其重要性。

# 粗体(Bold)

要加粗文本,请在单词或短语的前后各添加两个星号(asterisks)或下划线(underscores)。如需加粗一个单词或短语的中间部分用以表示强调的话,请在要加粗部分的两侧各添加两个星号(asterisks)。

Markdown语法 HTML 预览效果
I just love **bold text**. I just love <strong>bold text</strong>. I just love bold text.
I just love __bold text__. I just love <strong>bold text</strong>. I just love bold text.
Love**is**bold Love<strong>is</strong>bold Loveisbold

# 粗体(Bold)用法最佳实践

Markdown 应用程序在如何处理单词或短语中间的下划线上并不一致。为兼容考虑,在单词或短语中间部分加粗的话,请使用星号(asterisks)。

✅  Do this ❌  Don’t do this
Love**is**bold Love__is__bold

# 斜体(Italic)

要用斜体显示文本,请在单词或短语前后添加一个星号(asterisk)或下划线(underscore)。要斜体突出单词的中间部分,请在字母前后各添加一个星号,中间不要带空格。

Markdown语法 HTML 预览效果
Italicized text is the *cat's meow*. Italicized text is the <em>cat's meow</em>. Italicized text is the cat’s meow.
Italicized text is the _cat's meow_. Italicized text is the <em>cat's meow</em>. Italicized text is the cat’s meow.
A*cat*meow A<em>cat</em>meow A_cat_meow

# 斜体(Italic)用法的最佳实践

要同时用粗体和斜体突出显示文本,请在单词或短语的前后各添加三个星号或下划线。要加粗并用斜体显示单词或短语的中间部分,请在要突出显示的部分前后各添加三个星号,中间不要带空格。

✅  Do this ❌  Don’t do this
A*cat*meow A_cat_meow

# 粗体(Bold)和斜体(Italic)

要同时用粗体和斜体突出显示文本,请在单词或短语的前后各添加三个星号或下划线。要加粗并用斜体显示单词或短语的中间部分,请在要突出显示的部分前后各添加三个星号,中间不要带空格。

Markdown语法 HTML 预览效果
This text is ***really important***. This text is <strong><em>really important</em></strong>. This text is really important.
This text is ___really important___. This text is <strong><em>really important</em></strong>. This text is really important.
This text is __*really important*__. This text is <strong><em>really important</em></strong>. This text is really important.
This text is **_really important_**. This text is <strong><em>really important</em></strong>. This text is really important.
This is really***very***important text. This is really<strong><em>very</em></strong>important text. This is really**very**important text.

# 粗体(Bold)和斜体(Italic)用法的最佳实践

Markdown 应用程序在处理单词或短语中间添加的下划线上并不一致。为了实现兼容性,请使用星号将单词或短语的中间部分加粗并以斜体显示,以示重要。

✅  Do this ❌  Don’t do this
This is really***very***important text. This is really___very___important text.