# C99和C11新特性
<font style="background:yellow">
1
C99 标准引入了许多特性,
内联函数(inline functions)
可变长度的数组
灵活的数组成员(用于结构体)
复合字面量
毫不夸张地说,即便到目前为止,很少有C语言编译器是完整支持 C99 的。
- 像主流的 GCC 以及 Clang 编译器都能支持高达90%以上,
- 而微软的 Visual Studio 2015 中的C编译器只能支持到 70% 左右。
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 目录
[TOC]
# ✅C99
# 1.C语言中_Bool
和bool
- C99和C11新增了
_Bool
类型的位字段!『或许在按时- 《C Primer Plus》第6版,P505
//测试环境64位CPU,64位编译器gcc
//gcc demo.c
#include <stdio.h>
struct node
{
_Bool b : 1;
_Bool c : 1;
}ss;
int main()
{
printf("%d\n", sizeof(ss) ); //输出1是因为:这是_Bool类型的位字段
printf("%d\n", sizeof(_Bool)); //输出1是因为:需要“字节对齐”
return 0;
}
//输出是,原因是
/*
1
1
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_Bool
是C语言中布尔变量的类型名!只能存储1或0规律1:如果把其他非0数值赋值为
_Bool
类型的变量,该变量会被设置为1_Bool temp=8; printf("%d\n",temp);//输出,发现是1『遵守规律1』
1
2
规律2:从真/假方面看:对C而言,表达式为真的值是1,表达式为假的值是0
规律3:从数值方面看:只要测试条件是非0都会被视为真,只有0被视为假
- 《C Primer Plus》第6版,P145-P148
C99提供了
stdbool.h
头文件,让bool
成为_Bool
的别名,而且把true
和false
分别定义为1和0
的符号常量。包含该头文件后,写出的代码可以与C++
兼容,因为C++把bool、true和false定义为关键字!
已知有如下的变量定义,那么第二行的表达式的值是多少()
int main(void)
{
int x = 3 , y = 4 , z = 5;
!(x + y) + z-1 && y + z/2;
return 0;
}
正确答案: D
A 6
B 2
C 0
D 1
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- 1、if和while啥的判断一个int,只要不是0,就认为是true,if( -1 )效果和if(true)一样
- 2、我们用判断a && b啥得到的true和false如果用int承载却直接对应的是
1和0
!!! - 3、(注意记住上面2点)
# 2.变长数组variable-length array,VLA
⚡️
- 被牛客上一些牛油,误解为『柔性数组』
- 《C Primer Plus》第6版,P309
sum2d ()函数之所以能处理这些数组,是因为这些数组的列数固定为4,而行数被传递给形参rows,rows是一个变量。但是如果要计算6×5的数组(即6行5列),就不能使用这个函数,必须重新创建一个CLOs为5的函数。
- 因为C规定,数组的维数必须是常量,不能用变量来代替cOLS。
1
2
3
2
3
鉴于此,C99新增了变长数组(variable-length array,VLA),允许使用变量表示数组的维度。如下所示:
int quarters = 4;
int regions = 5;
double sales [regions] [ quarters]; //一个变长数组(VLA)
1
2
3
2
3
前面提到过,变长数组有一些限制。
- 变长数组必须是自动存储类别,这意味着无论在函数中声明还是作为函数形参声明,都不能使用static或extern存储类别说明符(第12章介绍)。
- 而且,不能在声明中初始化它们。『报错如下』
- 最终,C11把变长数组作为一个可选特性,而不是必须强制实现的特性。
//gcc -std=c99 solve.c
#include<stdio.h>
int main()
{
int a=10;
int solve[a]={9,8,1};
int i=0;
for(; i<10; ++i)
{
printf("%d\n",solve[i]);
}
return 0;
}
/*
olve.c: In function ‘main’:
solve.c:5:5: error: variable-sized object may not be initialized
int solve[a]={9,8,1};
^
solve.c:5:5: warning: excess elements in array initializer [enabled by default]
solve.c:5:5: warning: (near initialization for ‘solve’) [enabled by default]
solve.c:5:5: warning: excess elements in array initializer [enabled by default]
solve.c:5:5: warning: (near initialization for ‘solve’) [enabled by default]
solve.c:5:5: warning: excess elements in array initializer [enabled by default]
solve.c:5:5: warning: (near initialization for ‘solve’) [enabled by default]
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 但是,不初始化就行
//gcc -std=c99 solve.c
#include<stdio.h>
int main()
{
int a=10;
int solve[a];
int i=0;
for(; i<10; ++i)
{
printf("%d\n",solve[i]);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 3.伸缩型数组成员⚡️
- C99新增了一个特性,伸缩行数组成员(flexible array member)
- 《C Primer Plus》第6版,P463
struct flex
{
int count;
double average;
double scores[]; //伸缩型数组成员
};
1
2
3
4
5
6
2
3
4
5
6
# ♻️柔性数组成员(伸缩型数组成员)
- 柔性数组(Flexible Array)又称“可变长数组”
- 柔性数组(flexible array member)也叫伸缩性数组成员
- 注意:伸缩型数组成员≠变长数组
# 1.观察现象
//测试1
#include<stdio.h>
typedef struct list_t
{
struct list_t *next;
struct list_t *prev;
char data[0];
}list_t;
int main()
{
printf("%d",sizeof(list_t));
return 0;
}
/*
测试环境:32位,DevC++5.11的C语言编译器
输出:8
测试环境:32位,VS2012的C语言编译器
输出:8
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 测试2
将测试1中代码,char data[0];
改为char data[];
测试环境:32位,DevC++5.11的C语言编译器
输出:8
测试环境:32位,VS2012的C语言编译器
输出:8
1
2
3
4
2
3
4
- 柔性数组解释:
这是因为在定义这个结构体的时候,结构体的大小
已经不包含
柔性数组的内存大小了。
柔性数组不占结构体内。只是在使用的时候需要把它当作结构体的一个成员而已。 - 联想:这个说法,让我想起了C++类中的static变量,本质是全局变量,所以其实不占那个类的内存,但是我把它放在类里面只是为了告知,这个变量和这个类有联系
...
# 2.编译器支持情况
- 柔性数组是『『C99』』引入的一个新特性
- 这个特性允许你在定义结构体的时候创建一个空数组,而这个数组的大小可以在程序运行的过程中根据你的需求进行更改(也就是可变长长度的数组),这样我们在结构体中定义一个柔性数组,这样可以确保能够在程序运行过程中“动态”的进行结构体的扩展!
- 特别注意:
- 1)这个空数组必须声明为结构体的最后一个成员
- 2)并且还要求这样的结构体至少包含一个其他类型的成员
PS——摘自别人的注解:
> 1)其实,C99支持的标准是`char address[]`,按理来说`char address[0]`是非法的,只不过,有的编译器把它当做非标准扩展了。
> 2)此外,在C99之前,就有这样的写法,C99出来后,有些编译器把两者等同了。
所以,我才能在32位,**DevC++5.11的C语言编译器(支持的是C89)**这样的测试环境下,还能使用这种写法。
此外,在很多编译器中,对这个的支持有点差异,具体使用,还是测试一下吧。 虽然,似乎很多版本gcc是OK的
1
2
3
4
5
2
3
4
5
# 3.用法
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//存放学生信息结构体
typedef struct{
int stuID;
int age;
char address[];
}ST_STU_INFO,*pStuInfo;
//为结构体分配内存
pStuInfo ComposeStuInfo( int stuID,int age, const char *paddress)
{
pStuInfo ptmpInfo = malloc(sizeof(*ptmpInfo) + sizeof(char) * strlen(paddress) + 1);
if(ptmpInfo != NULL){
ptmpInfo->stuID = stuID;
ptmpInfo->age = age;
strcpy(ptmpInfo->address, paddress);
}
return ptmpInfo;
}
// 打印学生信息
void printStuInfo(pStuInfo ptmpInfo)
{
printf("stuID : %d age : %d Address: %s\nSize of Struct:%d\n\n",
ptmpInfo->stuID,ptmpInfo->age,ptmpInfo->address,sizeof(*ptmpInfo));
}
//主程序
int main()
{
pStuInfo CodeLab = ComposeStuInfo(100013,20, "Tencent,Shenzhen");
if(CodeLab != NULL){
printStuInfo(CodeLab);
free(CodeLab);
}
pStuInfo subCodeLab = ComposeStuInfo(200013,23, "Tencent");
if(subCodeLab != NULL){
printStuInfo(subCodeLab);
free(subCodeLab);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
输出如下:
stuID : 100013 age : 20 Address: Tencent,Shenzhen
Size of Struct:8
stuID : 200013 age : 23 Address: Tencent
Size of Struct:8
1
2
3
4
5
2
3
4
5
分析:
- 1)用柔性数组,我们成功实现了用不同长度的数组,而程序不会由于越界等异常情况而崩溃
- 2)并且,在输出中,我们可以看到结构体的大小并没有因此而发生变化。
所以来看一道“美团点评”的笔试题:
开发C代码时,经常见到如下类型的结构体定义:
typedef struct list_t{
struct list_t *next;
struct list_t *prev;
char data[0];
}list_t;
最后一行char data[0];的作用是()
A、方便管理内存缓冲区
B、减少内存碎片化
C、标识结构体结束
D、没有作用
/*
答案是:A和B
1)针对柔性数组这一不占用内存的特性,可以构造出内存缓冲区,同时由于是使用多少就申请多少,也起到了减少内存碎片化的作用,所以答案是A和B
2)C选项,柔性数组并不是标识结构体结束,而是作为结构体的一种拓**展**
3)注意:构造缓冲区就是方便管理内存缓冲区,减少内存碎片化。
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.对比
//1、直接使用指针
typedef struct{
int stuID;
int age;
char *pAddress;
}ST_STU_INFO;
//2、使用固定长度大小的数组
typedef struct{
int stuID;
int age;
char pAddress[20];
}ST_STU_INFO;
//3、使用柔性数组
typedef struct{
int stuID;
int age;
char pAddress[];
}ST_STU_INFO;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
对比:
- 1)柔性数组不占用内存,而指针则不然,柔性数组在使用上是直接访问,形式上更加直观,而指针需要经过声明再进行动态分配内存,在效率上和柔性数组相比也稍微低一些。
- 2)柔性数组能够按需用内存,而固定长度的数组,由于可能用不了那么长就会导致浪费。要是用的超过固定长度,则会导致程序崩溃。比如下面这样:
设计初衷和用途
- Tips:在Linux内核代码中有较多的柔性数组的使用
- 设计初衷:主要用途是为了满足需要变长度的结构体,为了解决使用数组时内存的冗余和数组的越界问题
- 用途:
- 1)这样的变长数组常用于网络通信中构造不定长数据包,不会浪费空间浪费网络流量,比如我要发送1024字节的数据,如果用定长包,假设定长包的长度为2048,就会浪费1024个字节的空间,造成不必要的流量浪费。
- 2)**跳跃表(跳表,Skip list)**的实现上,可以使用柔性数组。
# 4.内联函数
- 《C Primer Plus》第6版,P542
# ❄️C99新特性-冷门
# t.1.指定初始化器材
- 指定初始化器(
designated initializer
)- 《C Primer Plus》第6版,P281
#include<stdio.h>
#define MONTHS 12
int main()
{
int days[MONTHS]={31,28, [4]=31, 30, 31, [1]=29 }; //指定初始化器
int i;
for( i=0; i<MONTHS; i++ )
{
printf("%2d %d\n", i+1, days[i] );
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# t2.复合字面值
- C99新增了复合字面值(compound literal)
- 《C Primer Plus》第6版,P312
# t3.tgmath.h
库
- 《C Primer Plus》第6版,P550
# ✅C11
# 1.匿名结构
- C11新增
- 《C Primer Plus》第6版,P465
struct person
{
int id;
struct {
char first[20];
char last[20];
}; //匿名结构
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 2.匿名联合
- C11
- 原理和匿名结构一样
- 《C Primer Plus》第6版,P473
# 3.对齐特性
- 《C Primer Plus》第6版,P515
# 4.泛型选择
- 《C Primer Plus》第6版,P541
# ❄️C11新特性-冷门
# t1._Atomic
类型限定符
- 『编译器不一定支持』
- 《C Primer Plus》第6版,P406
# t2._Noreturn
函数
- 《C Primer Plus》第6版,P544
# t3._Static_assert
- 《C Primer Plus》第6版,P557
# 参考资料
- 《C与指针》
- 《C专家编程》
- 《C缺陷与陷阱》
- 《C Primer Plus(第6版)》,普拉达 (Stephen Prata) (opens new window)/译者: 姜佑 (opens new window)
- 『CSDN C语言讲义』王保民
- 『C语言指针详解』
- 『C语言小白变怪兽 v1.0』,严长生,C语言中文网站长