• C 类型限定符


    C 类型限定符

    1. Introduction

    C 语言中的大部分类型都可以用称为限定符(qualifier)的关键字 const、 volatile、 restrict、 _Atomic 加以限定。这些限定符可以单独使用,也可以组合使用。

    const 和 volatile 在 C89/C90 版本定义,restrict 在 C99 版本定义,_Atomic 在 C11 版本定义,_Atomic 对于编译器而言是可选的支持特性,表示原子类型

    一个类型不加限定符的版本和加限定符的版本是两个不同的类型,但是有相同的表示方法和对齐要求。
    因此,int 是一个类型,const int 是另一个类型,限定的结果是产生一个新的类型,而不是“仅仅加入一个修饰,使其具有只读属性”。

    程序员可以通过限定符提供一定的信息,以便 C 实现可以更好地优化程序。当编译器编译程序时,看到这些限定符就知道哪些优化是可以做的,哪些优化是不可以做的。另外,还可以检查出程序中的不当操作

    类型限定符有以下的使用规则:

    a. 类型限定符和类型指定符的顺序可交换

    const int i;
    int const i;
    

    上述两种表示方式是等价的,但是处于可读性的考虑,通常采用第一种表示方式。

    b. 派生类型不会继承类型限定符

    指针派生自它所指向的类型。类型 const int 可以派生出 const int *,该指针的类型是“指向一个 const int 类型的指针”,而不是具有 const 限定的指针。
    如果要生成 const 指针,需要对指针单独限定:

    int * const cp;		// cp 是 const 指针,派生自无限定的 int 类型
    const int * const cpc;	// cpc 是 const 指针,派生自 const int 类型
    

    struct 类型派生自它的成员类型,如下所示:

    struct t {const int i; const float f;}
    

    struct t 派生自两个成员类型 const int, const float,但是 struct t 不是 const 限定的类型。

    c. 同一个类型限定符同时出现多次,相当于只出现一次

    两个声明指定符连用是非法的,但是两个类型限定符连用是合法的:

    int int i;	// 非法
    const volatile const const int i;	// 等效于 const volatile int i
    int function(const const int i);	// 等效于 int function(const int i);
    

    2. const

    const 比较准确的含义是"只读",而不是“常量”,“常量”在 C 语言中有特定的含义,与 const 无关。
    const 有以下几点需要了解:

    a. 优化

    当程序中出现 const int i = 0 这样的代码时,它表示程序不准备修改对象 i 的存储值,编译器可以根据这种意图提供适当的优化。
    比如,编译器可以在第一次访问对象 i 时将它缓存起来,以后只需要使用这个缓存值而不需要浪费时间重新读取。

    b. 安全

    编译器会检查代码是否违反了 const 的限定规则,对变量做了不适当的修改操作。
    但是用 const 限定的对象并非不可改写的,可以通过强制类型转换来绕过这种限制。
    例如:

    int x = 0; 
    const int *p = &x;
    (*p)++;	//S1
    (*(int*)p)++;	//S2
    
    

    S1 是非法的,S2 是合法的,S2 的做法是非常危险的,其后果未定义。

    c.作用范围

    const 限定符有其作用范围,如果多段代码共享同一个对象,或者多个线程共享同一个对象,那么这个对象可能在一个范围内是 const 限定的,在另一个范围内则不是。
    例如:

    void f(const char* c)
    {
    	*c = 'x';	//S1,非法
    }
    
    void g(void)
    {
    	char a[3];
    	f(a);
    }
    
    

    上述数组 a 的元素在 g() 中是可修改的,在 f() 中是只读的, S1 代码段的操作是非法的。

    3. volatile

    与 const 相反,volatile 的含义是告诉 C 实现,对象的值会改变,并且是以不受控制的方式改变。
    如果一个类型是 “volatile 限定的类型”,则意味着该类型所定义的对象,它的值不单单会被当前程序的代码修改,还可能潜在地被其他程序或代码修改。
    因此,编译器不能在编译时对访问该对象的代码做优化处理,对这种对象的处理不能依赖于缓存特性。
    例如,它可能对应一个硬件的端口,或者几个程序或线程公用的存储位置等等。

    int i;
    interrupt_handler()
    {
    	i = 1;
    }
    
    void function()
    {
    	i = 0;
    	while (0 == i);
    }
    
    

    虽然本意是在中断服务函数中将 i 赋值为 1,但实际上 function 函数中的 while 循环可能永远不会退出。
    因为编译器看到在 while 循环前 i 被赋值为 0,while(0 == i) 的条件比较结果肯定为 1,如果打开了编译器的优化选项,while(0 == i) 会被优化为 while(1)
    解决方案就是在声明 i 的时候加入 volatile 限定符:volatile int i;
    这样编译器就不会做出不适当的优化。

    4. restrict

    restrict 限定符仅适用于指针类型
    如果一个指针类型是 restrict 限定的,则它所指向的对象和该指针有一种特殊的联系:在一个代码块内(函数体或者复合语句),所有到这个对象的引用必须直接或者间接通过这个指针进行
    基于上述保证的条件,编译器就可以在代码块的开始处安全地缓存“该指针所指向的对象”的值,读取和更新操作只针对这个缓存值进行。在退出代码块之前,再将缓存的值写回到指针所指向的对象。
    如果没有这个限定符,则意味着在当前块内,还可能存在着其他指向这个对象的指针,因此,缓存对象的值是不安全的。
    以下两个代码段:

    void f1(int *p1, int *p2, int n)
    {
    	for (int i = 0; i < n; i++) {
    		*p1 ++;
    		*p2 ++
    	}
    }
    
    void f2(int * restrict p1, int * restrict p2, int n)
    {
    	for (int i = 0; i < n; i++) {
    		*p1 ++;
    		*p2 ++
    	}
    }
    

    用相同的方法调用上述 f1 和 f2:

    int i = 0;
    f1(&i, &i, 10)
    
    int i = 0;
    f2(&i, &i, 10)
    

    f1 调用完成后,i 的值是 90;f2 调用完成后,i 的值是 45
    原因在于,在 f2 中,参数 p1 和 p2 都被声明为 restrict 限定,但调用时他们又指向了同一个对象,这违背了 restrict 的约定。
    因此在 f2 内部,for 循环里的两个累加过程都认为只有自己在改变对象的值,因此都使用了缓存值,而不是实际访问对象的值。
    当退出 for 循环时他们各自的值都是 45,退出 f2 函数前,这两个缓存值被各自刷新到 i 中,导致 i 的值是 45。

  • 相关阅读:
    SourceInsight3.5中文乱码问题解决
    执行脚本程序出现gzip:stdin:not in gzip format的解决方法
    libjpeg实现arm板上yuv420p转jpg
    yuv420p转jpg linux(纯C语言实现)
    linux下交叉编译libjpeg库并移植到开发板上
    关于arm板上ifup eth0出现问题的解决方法
    关于windows与ubuntu两台主机传输文件的一个小软件
    数据结构之查找算法篇
    利用paramiko将服务器的文件批量匹配并下载到本地
    最近写了中药系统药理学数据库与分析平台的爬虫,可以交流 https://tcmspw.com/index.php
  • 原文地址:https://www.cnblogs.com/gctech/p/13451472.html
Copyright © 2020-2023  润新知