C99 版本特性


title: C99 版本特性
date: 2021-07-26 16:00:00


前言

本文性质为学习总结,若有错误敬请指正。

目录

  1. C语言版本历史
  2. C99 主要特性
  3. 新特性示例代码
  4. 文末

C主要版本

C 语言的标准化过程由国际标准化组织(ISO)和国际电工委员会(IEC)负责。以下是 C 语言各个版本的主要顺序:

  1. K&R C(1978年):

    • 由 Brian Kernighan 和 Dennis Ritchie 编写的《The C Programming Language》一书中定义的版本,通常被称为“K&R C”。
  2. C89/C90(1989/1990年):

    • 也称为 ANSI C 或 ISO C,是第一个被国际标准化组织(ISO)采纳的 C 语言标准。
    • 标准文档为 ISO/IEC 9899:1990。
  3. C95(1995年):

    • 对 C90 的一个小修订,主要增加了对 Unicode 的支持。
    • 标准文档为 ISO/IEC 9899:1990/Amd 1:1995。
  4. C99(1999年):

    • 引入了许多新特性,包括内联函数、变长数组(VLA)、复数类型、泛型选择等。
    • 标准文档为 ISO/IEC 9899:1999。
  5. C11(2011年):

    • 引入了多线程支持、_Static_assert、_Generic 关键字等新特性。
    • 标准文档为 ISO/IEC 9899:2011。
  6. C18(2018年):

    • 对 C11 的一个技术修订,主要包含了一些勘误和澄清,没有引入新的语言特性。
    • 标准文档为 ISO/IEC 9899:2018。

每个版本的 C 语言标准都在前一个版本的基础上进行了扩展和改进,以适应新的编程需求和技术发展。国内相关C语言书籍主要采用 C89/C90 标准。GCC编译器支持C99标准,但默认使用C89标准。使用 -std=c99 选项可以指定使用C99标准。

C99主要版本特性简述

C99 标准引入了许多新内容,以下是一些主要的特性:

  1. _Bool 类型:引入了 _Bool 类型,用于表示布尔值。

  2. stdbool.h 头文件:提供了 booltruefalse 宏,使得布尔类型的使用更加方便。

  3. 变长数组(VLA):允许数组的大小在运行时确定。

  4. 复合字面量:允许在表达式中直接创建结构体或数组的临时对象。

  5. 内联函数:通过 inline 关键字支持内联函数。当编译器遇到内联函数的调用时,它会将函数的代码直接插入到调用该函数的地方,而不是像普通函数那样通过函数调用机制来执行。这样可以减少函数调用的开销,提高程序的执行速度。需要注意的是,内联函数并不总是能提高性能,因为插入的代码可能会增加可执行文件的大小。因此,内联函数通常用于函数体较小且频繁调用的情况。

  6. 灵活的结构体成员:允许结构体的最后一个成员具有可变长度。

  7. restrict 关键字:用于指针,表示该指针是访问其指向对象的唯一方式。

  8. long long 类型:引入了 long long 类型,提供更大的整数范围。

  9. 复数类型:引入了 _Complex_Imaginary 类型,用于处理复数。

  10. stdint.hinttypes.h 头文件:提供了固定宽度的整数类型和相应的格式化宏。

  11. 预处理器改进:包括 _Pragma 操作符和 __func__ 预定义标识符。

  12. 浮点环境访问:允许程序访问浮点环境的状态。

  13. va_copy:用于复制可变参数列表。

  14. __STDC_VERSION__:用于检测编译器是否支持 C99 标准。

这些特性使得 C99 在功能和灵活性上有了显著的提升。

示例代码

  1. _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 类型的变量,可以赋值为 truefalse

  2. 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;
    }
    
  3. 变长数组(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循化初始化。

  4. 复合字面量

    #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}来创建数组对象。

  5. 内联函数

    #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;
    }
    
  6. 灵活的结构体成员

    #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;
    }
    
  7. 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 修饰的指针确实是唯一的访问路径。如果违反了这个假设,可能会导致未定义行为。

  1. long long 类型

    #include <stdio.h>
    int main() {
        long long ll = 123456789012345LL;
        printf("Long long value: %lld\n", ll);
        return 0;
    }
    
  2. 复数类型

    #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;
    }
    
  3. stdint.hinttypes.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;
    }
    
  4. 预处理器改进

    #include <stdio.h>
    #define PRINT_FUNC() printf("Function: %s\n", __func__)
    void myFunction() {
        PRINT_FUNC();
    }
    int main() {
        myFunction();
        return 0;
    }
    

    详细见C99_Preprocessor_Improvements

  5. 浮点环境访问

    #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

  6. 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,表示程序正常结束
    }
    
  7. __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 语言的一次重要更新,引入了许多新的特性和改进,以增强语言的功能和灵活性。

文末

参考文献

  1. ISO/IEC 9899:1999 - Programming languages — C. International Organization for Standardization, 1999.
  2. ISO/IEC 9899:2011 - Programming languages — C. International Organization for Standardization, 2011.
  3. ISO/IEC 9899:2018 - Programming languages — C. International Organization for Standardization, 2018.

推荐阅读

  1. 书籍

    • 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.
  2. 在线资源

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值