卷首语:Qualifier,有的叫修饰符,有的叫限定符。除了const外,其余都很少用到,但又必须掌握,尤其是对于嵌入式编程,直接操作一些硬件寄存器,若修饰符使用不当,很可能就会遇到一些让自己一头雾水的现象。 C语言的四个限定符C90新增:const 和 volatile C99新增:restrict C11新增:_Atomic 备注: (1)C89、C90、ANSI C通常指的是同一个C语言标准。1989年,美国国家标准协会(ANSI)推出C语言和C标准库的标准。该标准通常被称为ANSI C。由于该标准是1989年推出的,因此也被称为C89。时隔一年,1990国际标准协会ISO参照ANSI标准,推出一模一样的C语言和C标准库标准,由于该标准是1990年提出的,因此被称为C90标准。因此,C89, C90, ANSI C是同一个标准。 (2)可以通过给编译器传递参数来指定所用标准,但一般编译器对C99和C11的支持都是不完整的。如何区别?看下是否编译报错即可。 限定符一:const最为常用,用于限定变量为constancy(很久不变),即常量。举几个例子,说明其用法和注意事项。 const用法说明 const 用法均不难理解,唯独指针比较列外,因为要区分是限定指针本身为const还是限定指针指向的值为const。如上图所述,const放在*左边,限定的是指针指向的值;放在*的右边,限定的是指针本身,不能再指向别的地方。 const int *P1: 在*左边,P1指向一个const int常量,*P1不能被修改,但P1可以被修改int * const P2 : 在*右边,P2是一个const指针, P2不能被修改,但*P2可以被修改const int *const P3:表面P3不能指向别处,其指向的数据,*P3也不能被修改 经典用法(1): 在constant.h中: static const double PI = 3.1415926; 定义常量的时候,其与地方只能引用式声明,如果给const常量加上一个static修饰符,则可以被所有文件所包含(include),只不过,每个文件都将有一个拷贝,如果是大数组之类,就比较浪费空间。 好处就是不必在别的文件,到处使用引用式声明: extern const double PI; 经典用法(2): 避免多次拷贝,也可以在constant.c中定义: const double PI = 3.1415926; 然后在constant.h中使用引用式声明,使用者include 'constant.h' 即可: extern const double PI; 还有更好的用法的同学,可以评论分享。 限定符二:volatile其语法和const一样。作用是告诉编译器其所修饰的变量,不可被优化。 举例1: int *x = p; a = *x; /..一段没有使用x的代码.../ b = *x; 如果不适用volatile限定 int x变量,编译器会认为x值在这段代码中是不变的,于是将x临时存到一个空闲的寄存器中,即cache优化,这样使用x的值的时候,直接从寄存器中读取,就能提升效率。 但如果在另外一个线程里,如果发生了 *p=其他值 ,即有其他人能修改了x指向的值,就会发生数据的不一致错误。这个时候就需要使用volatile。 举例2: char *p = &led_address;//如果LED亮度为0~255; *p=100; /* 省略一段与*p无关的代码 */ *p=255; 在这段代码中,本来应该看到LED亮度先是100,后续再变为255(最亮),但是因为没有volatile修饰的关系,某些编译器可能会将*p直接合并为一句,在最后直接令*p=255,这样就看不到LED灯亮度的变化过程。 限定符三:restrict与volatile相反,restrict告知编译器,其所修饰的变量,可以被优化,只能用于指针,表明所修饰的指针变量,是所指数据的唯一访问者。 同样的,restrict 也可告诉读者,restrict指针所指向的数据,不要且不能再有其他访问者。 int * restrict rP = (int *) malloc(10 * sizeof(int)); for(int i=0; i<10; i++) { rp[i]+=1; rp[i]+=3; }在这种情况下,编译器得知 rP 是所指内存的唯一访问者,会将 rp[i]+=1; rp[i]+=3;合并为一句: rp[i]+=4; 限定符四:_Atomic即原子类型。在多线程编程中,对于多个线程均可访问的变量,需要保证其数据的一致性,即当一个线程对一个原子类型的对象执行原子操作时(读或写),其他线程不能访问它,否则可能导致数据的不一致。 C11通过包含可选的头文件stdatomic.h和threads.h,提供了一些可选的(不是必须实现的)管理方法。值得注意的是,要通过各种宏函数来访问原子类型。 int DATA;// 普通声明DATA= 12; // 普通赋值转变为原子类型:_Atomic int DATA; // DATA是一个原子类型的变量atomic_store(&DATA, 12); // 修改原子类型DATA的数据,函数为stdatomic.h中的宏 卷尾语:我们通常用类型和存储类别来描述一个变量,本文是下面文章一个补充,完成对C语言变量另一个层面的解读。 |
|