C语言的发展历史大致上分为三个阶段:Old Style C、C89和C99.Ken Thompson和Dennis Ritchie发明C语言时有很多语法和现在并不一样,但为了向后兼容性(Backward Compatibility), 这些语法仍然在C89和C99中保留下来了.C89是最早的C语言规范,于1989年提出,1990年先由ANSI(美国国家标准委员会,American National Standards Institute)推出ANSI版本,后来被接纳为ISO国际标准(ISO/IEC 9899:1990),因而有时也称为C90,最经典的C语言教材[K&R]就是基于这个版本的,C89是目前最广泛采用的C语言标准,大多数编译器都完全支持C89.C99标准(ISO/IEC 9899:1999)是在1999年推出的,加入了许多新的特性,但目前仍没有得到广泛支持,在C99推出之后相当长的一段时间里,连gcc也没有完全实现C99的所有特性.
C99相对于C89或者ANSI C的特性
C99中增加了适用于指针的restrict类型修饰符,它是初始访问指针所指对象的惟一途径,因此只有借助restrict指针表达式才能访问对象.restrict指针主要用做函数形参,或者指向由malloc()函数所分配的内存变量.restrict数据类型不改变程序的语义.
如果某个函数定义了两个restrict指针形参,编译程序就假定它们指向两个不同的对象,memcpy()函数就是restrict指针的一个典型应用示例.C89中memcpy()函数原型如下:
void *memcpy (void *s1, const void *s2, size_t size);
如果s1和s2所指向的对象重叠,其操作就是未定义的.memcpy()函数只能用于不重叠的对象.
C99中memcpy()函数原型如下: void *memcpy(void *restrict s1, const void *restrict s2,size_t size);
通过使用restrict修饰s1和s2参数,可确保它们在该原型中指向不同的对象.
内联函数除了保持结构化和函数式的定义方式外,还能使程序员写出高效率的代码.函数的每次调用与返回都会消耗相当大的系统资源,尤其是当函数调用发生在重复次数很多的循环语句中时.一般情况下,当发生一次函数调用时,函数与参数需要进栈,各种寄存器内存需要保存.当函数返回时,寄存器的内容需要恢复,如果该函数在代码内进行内联扩展,当代码执行时,这些保存和恢复操作不会再发生,因为没有函数的进栈与退栈了,而且函数调用的执行速度也会大大加快,函数的内联扩展会产生较长的代码,所以只应该内联对应用程序性能有显著影响的函数以及长度较短的函数,即短小精悍且多次调用的函数.
_Bool值是0或1,C99中增加了用来定义bool、true以及false宏的头文件<stdbool.h>,以便程序员能够编写同时兼容于C与C++的应用程序,在编写新的应用程序时,应该使用<stdbool.h>头文件中的bool宏.
_Complex and _Imaginary
C99标准中定义的复数类型如下:
float_Complex; float_Imaginary; double_Complex; double_Imaginary; long double_Complex; long double_Imaginary.
<complex.h>头文件中定义了complex和imaginary宏,并将它们扩展为_Complex和_Imaginary,因此在编写新的应用程序时,应该使用<stdbool.h>头文件中的complex和imaginary宏.
long long int
C99标准中引进了long long int(-(2e63 - 1)至2e63 - 1)和unsigned long long int(0 - 2e64 - 1),long long int能够支
持的整数长度为64位.
可变长数组(VLA)
C99中,程序员声明数组时,数组的维数可以由任一有效的整型表达式确定,包括只在运行时才能确定其值的表达式,这类数组就叫做可变长数组.但是只有局部数组才可以是变长的.可变长数组的维数在数组生存期内是不变的,也就是说,可变长数组不是动态的.可以变化的只是数组的大小.可以使用*来定义不确定长的可变长数组.
在栈中分配VLA 时,仿佛调用了alloca 函数。无论其作用域如何,其生存期与通过调用alloca 在栈中分配数据时相同;直到函数返回时为止。如果在其中分配VLA 的函数返回时释放栈,则释放分配的空间。
实例如下:
#include <stdio.h> void foo(int); int main(void) { foo(4); return (0); } void foo(int n) { int i; int a[n]; for (i = 0; i < n; i++) { a[i] = n - i; } for (i = n - 1; i >= 0; i--) { printf("a[%d] = %d/n", i, a[i]); } }
example% cc test.c example% a.out a[3] = 1 a[2] = 2 a[1] = 3 a[0] = 4
数组声明中的类型修饰符
在C99中,如果需要使用数组作为函数参数,可以在数组声明的方括号内使用static关键字,这相当于告诉编译程序,参数所指向的数组将至少包含指定的元素个数,也可以在数组声明的方括号内使用restrict, volatile, const关键字,但只用于函数参数,如果使用restrict,指针是初始访问该对象的惟一途径,如果使用const,指针始终指向同一个数组,使用volatile没有任何意义,
字符// 引入包含直到(但不包括)新换行符的所有多字节字符的注释,除非// 字符出现在字符常量、字符串文字或注释中.
现在,C 编译器接受关于可执行代码的混合类型声明,如以下示例所示:
#include <stdio.h> int main(void) { int num1 = 3; printf("%d/n", num1); int num2 = 10; printf("%d/n", num2); return (0); }
C 编译器接受以下形式的#define 预处理程序指令:
#define identifier (…) replacement_list #define identifier (identifier_list, …) replacement_list
如果宏定义中的identifier_list 以省略号结尾,则意味着调用中的参数比宏定义中的参数(不包括省略号)多。否则,宏定义中参数的数目(包括由预处理标记组成的参数)与调用中参数的数目匹配。对于在其参数中使用省略号表示法的#define 预处理指令,在其替换列表中使用标识符__VA_ARGS__。以下示例说明可变参数列表宏工具。
#define debug(...) fprintf(stderr, __VA_ARGS__) #define showlist(...) puts(#__VA_ARGS__) //这里使用了字符化符号# #define report(test, ...) ((test)?puts(#test):printf(__VA_ARGS__)) debug(“Flag”); debug(“X = %d/n”,x); showlist(The first, second, and third items.); report(x>y, “x is %d but y is %d”, x, y); 其结果如下: fprintf(stderr, “Flag”); fprintf(stderr, “X = %d/n”, x); puts(“The first, second, and third items.”); ((x>y)?puts(“x>y”):printf(“x is %d but y is %d”, x, y));
_Pragma ( string-literal ) 形式的一元操作符表达式处理如下:
■ 如果字符串文字具有L 前缀,则删除该前缀。
■ 删除前导和结尾双引号。
■ 用双引号替换每个换码序列’。
■ 用单个反斜杠替换每个换码序列//。
预处理标记的结果序列作为pragma 指令中的预处理程序标记进行处理。
删除一元操作符表达式中的最初四个预处理标记。
与#pragma 比较,_Pragma 的优势在于:_Pragma 可以用于宏定义。
_Pragma(“string”) 与#pragma 字符串行为完全相同。考虑以下示例。首先列出示例的源代码,然后在预处理程序使其通过预处理之后,再列出示例的源代码。
#include <omp.h> #include <stdio.h> #define Pragma(x) _Pragma(#x) #define OMP(directive) Pragma(omp directive) void main() { omp_set_dynamic(0); omp_set_num_threads(2); OMP(parallel) { printf("Hello!/n"); } }
下面是预处理程序完成后的源代码。
void main() { omp_set_dynamic(0); omp_set_num_threads(2); # pragma omp parallel { printf("Hellow!/n"); } }
STDC FP_CONTRACT ON/OFF/DEFAULT
若为ON,浮点表达式被当做基于硬件方式处理的独立单元,默认值是定义的工具.
STDC FEVN_ACCESS ON/OFF/DEFAULT
告诉编译程序可以访问浮点环境,默认值是定义的工具.
STDC CX_LIMITED_RANGE ON/OFF/DEFAULT
若值为ON,相当于告诉编译程序某程序某些含有复数的公式是可靠的,默认是OFF.
STDC_HOSTED 若操作系统存在,则为1 STDC_VERSION 199991L或更高,代表C的版本 STDC_IEC_599 若支持IEC 60559浮点运算,则为1 STDC_IEC_599_COMPLEX 若支持IEC 60599复数运算,则为1 STDC_ISO_10646 由编译程序支持,用于说明ISO/IEC 10646标准的年和月格式:yyymmmL
C99中,程序员可以在for语句的初始化部分定义一个或多个变量,这些变量的作用域仅于本for语句所控制的循环体内.在C89中,这样是不可以的,具体可以在VC6中验证.VC6支持到C89.
C 编译器接受作为for 循环语句中第一个表达式的类型声明:
for (int i=0; i<10; i++){ //loop body };
for 循环的初始化语句中声明的任何变量的作用域是整个循环(包括控制和迭代表达式)。
C99中,复合赋值中,可以指定对象类型的数组、结构或联合表达式,当使用复合赋值时,应在括弧内指定类型,后跟由花括号围起来的初始化列表;若类型为数组,则不能指定数组的大小,建成的对象是未命名的,
例: double *fp = (double[]) {1.1, 2.2, 3.3};
该语句用于建立一个指向double的指针fp,且该指针指向这个3元素数组的第一个元素,在文件域内建立的复合赋值只在程序的整个生存期内有效,在模块内建立的复合赋值是局部对象,在退出模块后不再存在.
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少一个其他成员,柔性数组成员允许结构中包含一个大小可变的数组,sizeof返回的这种结构大小不包括柔性数组的内存,包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小.
C99中,该特性对经常使用稀疏数组的程序员十分有用,指定的初始化符通常有两种用法:用于数组,以及用于结构和联合.
用于数组的格式:[index] = vol; 其中,index表示数组的下标,vol表示本数组元素的初始化值,例如: int x[10] = {[0] = 10, [5] = 30}; 其中只有x[0]和x[5]得到了初始化.这种方式不错,但是VC6对他不支持.
用于结构或联合的格式如下:
member-name(成员名称) 对结构进行指定的初始化时,允许采用简单的方法对结构中的指定成员进行初始化.
例如: struct example{ int k, m, n; } object = {m = 10, n = 200};
其中,没有初始化k,对结构成员进行初始化的顺序没有限制.
指定的初始化函数为初始化稀疏数组提供了一种机制,这在数字编程的实践中很常见。指定的初始化函数可以对稀疏结构进行初始化,这在系统编程中很常见,并且可以通过任何成员对联合进行初始化,而不管其是否为第一个成员。
请看以下这些示例。此处的第一个示例显示了如何使用指定的初始化函数来对数组进行初始化:
enum { first, second, third }; const char *nm[] = { [third] = "third member", [first] = "first member", [second] = "second member", };
下面的示例证明了如何使用指定的初始化函数来对结构对象的字段进行初始化:
division_t result = { .quot = 2, .rem = -1 };
下面的示例显示了如何使用指定的初始化函数对复杂的结构进行初始化(否则这些结构可能会被误解):
struct { int z[3], count; } w[] = { [0].z = {1}, [1].z[0] = 2 };
通过使用单个指示符可以从两端创建数组:
通过使用单个指示符可以从两端创建数组:
int z[MAX] = {1, 3, 5, 7, 9, [MAX-5] = 8, 6, 4, 2, 0};
如果MAX 大于10,则数组将在中间位置包含取值为零的元素;如果MAX 小于10,则前五个初始化函数提供的某些值将被后五个初始化函数的值覆盖。
联合的任何成员均可进行初始化:
union { int i; float f;} data = { .f = 3.2 };
C99中printf()和scanf()函数系列引进了处理long long int和unsigned long long int数据类型的特性,long long int 类型的格式修饰符是ll,在printf()和scanf()函数中,ll适用于d, i, o, u 和x格式说明符.
另外,C99还引进了hh修饰符,当使用d, i, o, u和x格式说明符时,hh用于指定char型参数,ll和hh修饰符均可以用于n说明符.
格式修饰符a和A用在printf()函数中时,结果将会输出十六进制的浮点数,格式如下:[-]0xh, hhhhp + d 使用A格式修饰符时,x和p必须是大小,A和a格式修饰符也可以用在scanf()函数中,用于读取浮点数,调用printf()函数时,允许在%f说明符前加上l修饰符,即%lf,但不起作用.
C89中标准的头文件:
<assert.h> 定义宏assert() <ctype.h> 字符处理 <errno.h> 错误报告 <float.h> 定义与实现相关的浮点值勤 <limits.h> 定义与实现相关的各种极限值 <locale.h> 支持函数setlocale() <math.h> 数学函数库使用的各种定义 <setjmp.h> 支持非局部跳转 <signal.h> 定义信号值 <stdarg.h> 支持可变长度的参数列表 <stddef.h> 定义常用常数 <stdio.h> 支持文件输入和输出 <stdlib.h> 其他各种声明 <string.h> 字符串函数 <time.h> 支持系统时间函数
C99新增的头文件和库:
<complex.h> 支持复杂算法 <fenv.h> 给出对浮点状态标记和浮点环境的其他方面的访问 <inttypes.h> 定义标准的、可移植的整型类型集合,也支持处理最大宽度整数的函数 <iso646.h> 首先在此1995年第一次修订时引进,用于定义对应各种运算符的宏 <stdbool.h> 支持布尔数据类型类型,定义宏bool,以便兼容于C++ <stdint.h> 定义标准的、可移植的整型类型集合,该文件包含在<inttypes.h>中 <tgmath.h> 定义一般类型的浮点宏 <wchar.h> 首先在1995年第一次修订时引进,用于支持多字节和宽字节函数 <wctype.h> 首先在1995年第一次修订时引进,用于支持多字节和宽字节分类函数
用于指出__func__所存放的函数名,类似于字符串赋值.
类型限定符:
如果同一限定符在同一说明符限定符列表中出现多次(无论直接出现还是通过一个或多个typedef),行为与该类型限定符仅出现一次时相同。
在C90 中,以下代码会导致错误:
const const int a; int main(void) { return (0); } %example cc -xc99=none test.c "test.c", line 1: invalid type combination
但是,对于C99,C 编译器接受多个限定符。
%example cc -xc99 test.c %example
数组声明符:
现在,关键字static 可以出现在函数声明符中参数的数组声明符中,表示编译器至少可以假定许多元素将传递到所声明的函数中。使优化器能够作出以其他方式无法确定的假定。
C 编译器将数组参数调整为指针,因此void foo(int a[]) 与void foo(int *a) 相同。 如果您指定void foo(int * restrict a); 等类型限定符,则C 编译器使用实质上与声明限定指针相同的数组语法void foo(int a[restrict]); 表示它。
C 编译器还使用static 限定符保留关于数组大小的信息。例如,如果您指定void foo(int a[10]),则编译器仍将它表达为void foo(int *a)。按以下所示使用static 限定符:void foo(int a[static 10]),让编译器知道指针a 不是NULL,并且使用它可访问至少包含十个元素的整数数组。
其它特性的改动:
放宽的转换限制
限制 | C89标准 | C99标准 |
---|---|---|
数据块的嵌套层数 | 15 | 127 |
条件语句的嵌套层数 | 8 | 63 |
内部标识符中的有效字符个数 | 31 | 63 |
外部标识符中的有效字符个数 | 6 | 31 |
结构或联合中的成员个数 | 127 | 1023 |
函数调用中的参数个数 | 31 | 127 |
不再支持隐含式的int规则和隐式函数声明.
每个声明中的声明说明符中应至少指定一个类型说明符,现在不支持没有类型就默认是int的声明语句.比如在C89中, auto i = 0;是合法的.
现在,C 编译器会对任何隐式int 声明都发出警告,如以下示例所示:
volatile i; const foo() { return i; } cc test.c "test.c", line 1: warning: no explicit type given "test.c", line 3: warning: no explicit type given
对返回值的约束,C99中,非空类型函数必须使用带返回值的return语句.
扩展的整数类型
扩展类型 | 含义 |
---|---|
int16_t | 整数长度为精确16位 |
int_least16_t | 整数长度为至少16位 |
int_fast32_t | 最稳固的整数类型,其长度为至少32位 |
intmax_t | 最大整数类型 |
uintmax_t | 最大无符号整数类型 |
对整数类型提升规则的改进:
C89中,表达式中类型为char,short int或int的值可以提升为int或unsigned int类型.
C99中,每种整数类型都有一个级别.例如:long long int 的级别高于int, int的级别高于char等.在表达式中,其级别低于int或unsigned int的任何整数类型均可被替换成int或unsigned int类型.