# 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

# 目录

[TOC]

# ✅C99

# 1.C语言中_Boolbool

  • 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
  • _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的别名,而且把truefalse分别定义为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
  • 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

鉴于此,C99新增了变长数组(variable-length array,VLA),允许使用变量表示数组的维度。如下所示:

int quarters = 4;
int regions = 5;
double sales [regions] [ quarters]; //一个变长数组(VLA)
1
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
  • 但是,不初始化就行
//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

# 3.伸缩型数组成员⚡️

  • C99新增了一个特性,伸缩行数组成员(flexible array member)
  • 《C Primer Plus》第6版,P463
struct flex
{
    int count;
    double average;
    double scores[];	//伸缩型数组成员
};
1
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

将测试1中代码,char data[0];改为char data[];

测试环境:32位,DevC++5.11的C语言编译器
输出:8
测试环境:32位,VS2012的C语言编译器
输出:8
1
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

# 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

输出如下:

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

分析:

  • 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

# 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

对比:

  • 1)柔性数组不占用内存,而指针则不然,柔性数组在使用上是直接访问,形式上更加直观,而指针需要经过声明再进行动态分配内存,在效率上和柔性数组相比也稍微低一些。
  • 2)柔性数组能够按需用内存,而固定长度的数组,由于可能用不了那么长就会导致浪费。要是用的超过固定长度,则会导致程序崩溃。比如下面这样:

2020_06_12_Flexible Array_01

设计初衷和用途

  • 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

# 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.匿名联合

  • 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

# 参考资料