title: C99 版本特性
date: 2021-07-26 16:00:00
前言
本文性质为学习总结,若有错误敬请指正。
目录
- C语言版本历史
- C99 主要特性
- 新特性示例代码
- 文末
C主要版本
C 语言的标准化过程由国际标准化组织(ISO)和国际电工委员会(IEC)负责。以下是 C 语言各个版本的主要顺序:
-
K&R C(1978年):
- 由 Brian Kernighan 和 Dennis Ritchie 编写的《The C Programming Language》一书中定义的版本,通常被称为“K&R C”。
-
C89/C90(1989/1990年):
- 也称为 ANSI C 或 ISO C,是第一个被国际标准化组织(ISO)采纳的 C 语言标准。
- 标准文档为 ISO/IEC 9899:1990。
-
C95(1995年):
- 对 C90 的一个小修订,主要增加了对 Unicode 的支持。
- 标准文档为 ISO/IEC 9899:1990/Amd 1:1995。
-
C99(1999年):
- 引入了许多新特性,包括内联函数、变长数组(VLA)、复数类型、泛型选择等。
- 标准文档为 ISO/IEC 9899:1999。
-
C11(2011年):
- 引入了多线程支持、_Static_assert、_Generic 关键字等新特性。
- 标准文档为 ISO/IEC 9899:2011。
-
C18(2018年):
- 对 C11 的一个技术修订,主要包含了一些勘误和澄清,没有引入新的语言特性。
- 标准文档为 ISO/IEC 9899:2018。
每个版本的 C 语言标准都在前一个版本的基础上进行了扩展和改进,以适应新的编程需求和技术发展。国内相关C语言书籍主要采用 C89/C90 标准。GCC编译器支持C99标准,但默认使用C89标准。使用 -std=c99
选项可以指定使用C99标准。
C99主要版本特性简述
C99 标准引入了许多新内容,以下是一些主要的特性:
-
_Bool
类型:引入了_Bool
类型,用于表示布尔值。 -
stdbool.h
头文件:提供了bool
、true
和false
宏,使得布尔类型的使用更加方便。 -
变长数组(VLA):允许数组的大小在运行时确定。
-
复合字面量:允许在表达式中直接创建结构体或数组的临时对象。
-
内联函数:通过
inline
关键字支持内联函数。当编译器遇到内联函数的调用时,它会将函数的代码直接插入到调用该函数的地方,而不是像普通函数那样通过函数调用机制来执行。这样可以减少函数调用的开销,提高程序的执行速度。需要注意的是,内联函数并不总是能提高性能,因为插入的代码可能会增加可执行文件的大小。因此,内联函数通常用于函数体较小且频繁调用的情况。 -
灵活的结构体成员:允许结构体的最后一个成员具有可变长度。
-
restrict
关键字:用于指针,表示该指针是访问其指向对象的唯一方式。 -
long long
类型:引入了long long
类型,提供更大的整数范围。 -
复数类型:引入了
_Complex
和_Imaginary
类型,用于处理复数。 -
stdint.h
和inttypes.h
头文件:提供了固定宽度的整数类型和相应的格式化宏。 -
预处理器改进:包括
_Pragma
操作符和__func__
预定义标识符。 -
浮点环境访问:允许程序访问浮点环境的状态。
-
va_copy
宏:用于复制可变参数列表。 -
__STDC_VERSION__
宏:用于检测编译器是否支持 C99 标准。
这些特性使得 C99 在功能和灵活性上有了显著的提升。
示例代码
-
_Bool
类型:#include <stdio.h> int main() { _Bool b = 1; if (b) { printf("b is true\n"); } else { printf("b is false\n"); } return 0; }
这里,
b
是一个_Bool
类型的变量,可以赋值为true
或false
。 -
stdbool.h
头文件:#include <stdio.h> #include <stdbool.h> int main() { bool b = true; if (b) { printf("b is true\n"); } else { printf("b is false\n"); } return 0; }
-
变长数组(VLA):
#include <stdio.h> void printArray(int n) { int array[n]; for (int i = 0; i < n; i++) { array[i] = i; printf("%d ", array[i]); } printf("\n"); } int main() { printArray(5); return 0; }
这里,
printArray
函数接受一个整数参数n
,并打印从 0 到n-1
的数组元素。但是,n
的值在运行时确定,因此数组的大小也会随之变化。因此变长数组又称为for循化初始化。 -
复合字面量:
#include <stdio.h> struct Point { int x, y; }; void printPoint(struct Point p) { printf("Point: (%d, %d)\n", p.x, p.y); } int main() { printPoint((struct Point){3, 4}); return 0; }
即可以使用
(struct Point){3, 4}
来创建结构体对象,也可以使用(int[]){1, 2, 3}
来创建数组对象。 -
内联函数:
#include <stdio.h> //GCC要求内联函数在调用之前必须有声明 int square(int x); inline int square(int x) { return x * x; } int main() { printf("Square of 5 is %d\n", square(5)); return 0; }
-
灵活的结构体成员:
#include <stdio.h> #include <stdlib.h> struct FlexArray { int count; int array[]; }; int main() { struct FlexArray *fa = malloc(sizeof(struct FlexArray) + 5 * sizeof(int)); fa->count = 5; for (int i = 0; i < fa->count; i++) { fa->array[i] = i; printf("%d ", fa->array[i]); } printf("\n"); free(fa); return 0; }
-
restrict
关键字:#include <stdio.h> void copy(int *restrict dest, const int *restrict src, int n) { for (int i = 0; i < n; i++) { dest[i] = src[i]; } } int main() { int src[] = {1, 2, 3, 4, 5}; int dest[5]; copy(dest, src, 5); for (int i = 0; i < 5; i++) { printf("%d ", dest[i]); } printf("\n"); return 0; }
这里,dest 和 src 都被声明为 restrict,这意味着编译器可以假设在 copy 函数执行期间,dest 和 src 指向的内存区域不会通过其他指针被修改。这为编译器提供了优化循环内部代码的机会,例如展开循环或使用更高效的寄存器分配策略。
需要注意的是,restrict 关键字只是一个编译器提示,程序员有责任确保通过 restrict 修饰的指针确实是唯一的访问路径。如果违反了这个假设,可能会导致未定义行为。
-
long long
类型:#include <stdio.h> int main() { long long ll = 123456789012345LL; printf("Long long value: %lld\n", ll); return 0; }
-
复数类型:
#include <stdio.h> #include <complex.h> int main() { double complex z = 1.0 + 2.0 * I; printf("Complex number: %f + %fi\n", creal(z), cimag(z)); return 0; }
-
stdint.h
和inttypes.h
头文件:#include <stdio.h> #include <stdint.h> #include <inttypes.h> int main() { int32_t x = 123456789; printf("int32_t value: %" PRId32 "\n", x); return 0; }
-
预处理器改进:
#include <stdio.h> #define PRINT_FUNC() printf("Function: %s\n", __func__) void myFunction() { PRINT_FUNC(); } int main() { myFunction(); return 0; }
详细见C99_Preprocessor_Improvements
-
浮点环境访问:
#include <stdio.h> #include <fenv.h> // 主函数,程序的入口点 int main() { // 启用标准C环境访问 #pragma STDC FENV_ACCESS ON // 清除所有浮点异常 feclearexcept(FE_ALL_EXCEPT); // 定义一个双精度浮点数并进行除以零的操作 double a = 1.0 / 0.0; // 检查是否发生了除以零的异常 if (fetestexcept(FE_DIVBYZERO)) { // 如果发生了除以零的异常,打印提示信息 printf("Division by zero occurred\n"); } // 返回0,表示程序正常结束 return 0; }
详细见C99_Floating_Point_Environment
-
va_copy
宏:#include <stdio.h> #include <stdarg.h> // 可变参数函数,打印所有传入的整数参数 void printArgs(int count, ...) { va_list args, args_copy; // 定义两个va_list类型的变量,用于处理可变参数 va_start(args, count); // 初始化args,使其指向第一个可变参数 va_copy(args_copy, args); // 复制args到args_copy for (int i = 0; i < count; i++) { // 遍历所有可变参数 printf("%d ", va_arg(args_copy, int)); // 打印当前参数的值 } printf("\n"); // 打印换行符 va_end(args); // 清理args va_end(args_copy); // 清理args_copy } // 主函数,程序入口 int main() { printArgs(3, 1, 2, 3); // 调用printArgs函数,传入三个参数 return 0; // 返回0,表示程序正常结束 }
-
__STDC_VERSION__
宏:#include <stdio.h> int main() { #ifdef __STDC_VERSION__ printf("C Standard version: %ld\n", __STDC_VERSION__); #else printf("C Standard version not defined\n"); #endif return 0; }
这些示例展示了 C99 标准中引入的一些主要特性。
C99 标准是在 1999 年更新的。这个标准是对 C 语言的一次重要更新,引入了许多新的特性和改进,以增强语言的功能和灵活性。
文末
参考文献
- ISO/IEC 9899:1999 - Programming languages — C. International Organization for Standardization, 1999.
- ISO/IEC 9899:2011 - Programming languages — C. International Organization for Standardization, 2011.
- ISO/IEC 9899:2018 - Programming languages — C. International Organization for Standardization, 2018.
推荐阅读
-
书籍
- Kernighan, B. W., & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall.
- King, K. N. (2008). C Programming: A Modern Approach (2nd ed.). W. W. Norton & Company.
- Prata, S. (2004). C Primer Plus (5th ed.). Sams Publishing.
- van der Linden, P. (1994). Expert C Programming: Deep C Secrets. Prentice Hall.
-
在线资源
- cppreference.com. (n.d.). Retrieved from cppreference.com
- GCC, the GNU Compiler Collection. (n.d.). Retrieved from gcc.gnu.org