一、相关概念
主要解释存储类关键字(auto static extern register)和类型限定、修饰关键字(const volatile restrict inline)等。在介绍之前,阐述几个相关概念:
1、存储类
(1)存储类就是就是描述C语言变量在何种地方存储,即栈、堆、数据段、bss段、.text段等。
2、作用域
(1)作用域是描述这个变量起作用的代码范围。
(2)基本来说,C语言变量的作用域规则是代码块作用域。意思就是这个变量起作用的范围是当前的代码块。代码块就是一对大括号{}括起来的范围,所以一个变量的作用域是:这个变量定义所在的{}范围内从这个变量定义开始往后的部分。(这就解释了为什么变量定义总是在一个函数的最前面)
3、生命周期
生命周期是描述这个变量什么时候诞生(运行时分配内存空间给这个变量)及什么时候死亡(运行时收回这个内存空间,此后再不能访问这个内存地址,或者访问这个内存地址已经和这个变量无关了)的。
4、链接属性
(1)程序从源代码到最终可执行程序,经历的过程:编译、链接。
(2)编译阶段就是把源代码搞成.o目标文件,目标文件里面有很多符号和代码段、数据段、bss段等分段。符号就是编程中的变量名、函数名等。运行时变量名、函数名能够和相应的内存对应起来,靠符号来做链接的。
(3).o的目标文件链接生成最终可执行程序的时候,其实就是把符号和相对应的段给链接起来。
(4)编译以文件为单位、链接以工程为单位。编译器工作时是将所有源文件依次读进来,单个为单位进行编译的。链接的时候实际上是把第一步编译生成个单个的.o文件整体的输入,然后处理链接成一个可执行程序。
C语言中的符号有三种链接属性:外连接属性、内链接属性、无连接属性。
- 外连接的意思就是外部链接属性,也就是说这家伙可以在整个程序范围内(言下之意就是可以跨文件)进行链接,譬如普通的函数和全局变量属于外连接。
- 内链接的意思就是(c文件内部)内部链接属性,也就是说这家伙可以在当前c文件内部范围内进行链接(言下之意就是不能在当前c文件外面的其他c文件中进行访问、链接)。static修饰的函数/全局变量属于内链接。
- 无连接的意思就是这个符号本身不参与链接,它跟链接没关系。所有的局部变量(auto的、static的)都是无连接的,宏和inline函数的链接属性为无连接。
函数和全局变量的同名冲突
(1)因为函数和全局变量是外部链接属性,就是说每一个函数和全局变量将来在整个程序中所有的c文件都能被访问,因此在一个程序中的所有c文件中不能出现同名的函数/同名的全局变量。
(2)最简单的解决方案就是起名字不要重复,但是在一个多人协作的大工程里很难做到。现代高级语言中完美解决这个问题的方法是命名空间namespace(其实就是给一个变量带上各个级别的前缀)。但是C语言不是这么解决的。就是三种链接属性的方法。即:将明显不会在其他c文件中引用(只在当前c文件中引用)的函数/全局变量,使用static修饰使其成为内链接属性,这样在将来连接时即使2个c文件中有重名的函数/全局变量,只要其中一个或2个为内链接属性就没事。所以写程序尽量避免使用全局变量,尤其是非static类型的全局变量。能确定不会被其他文件引用的全局变量一定要static修饰。
二、关键字解析
1、auto
(1)auto关键字在C语言中修饰局部变量。表示这个局部变量是自动局部变量,自动局部变量分配在栈上。(既然在栈上,说明它如果不初始化那么值就是随机的)
(2)平时定义局部变量时就是定义的auto的,只是省略了auto关键字而已。可见,auto的局部变量其实就是默认定义的普通的局部变量。
2、static
(1)static关键字在C语言中有2种用法,而且这两种用法彼此没有任何关联、完全是独立的。
(2)static的第一种用法是:用来修饰局部变量,形成静态局部变量。要搞清楚静态局部变量和非静态局部变量的区别。本质区别是存储类不同(存储类不同就衍生出很多不同):非静态局部变量分配在栈上,而静态局部变量分配在数据段/bss段上。
(3)static的第二种用法是:用来修饰全局变量,形成静态全局变量。静态全局变量和非静态全局变量的区别。区别是在链接属性上不同。
1、静态局部变量在存储类方面和全局变量一样。
2、静态局部变量在生命周期方面和全局变量一样。
3、静态局部变量和全局变量的区别是:作用域、连接属性。静态局部变量作用域是代码块作用域(和普通局部变量是一样的)、链接属性是无连接;全局变量作用域是文件作用域(和函数是一样的)、链接属性方面是外连接。
3、register
(1)register关键字修饰的变量,编译器会尽量(不保证一定放在寄存器中。主要原因是因为寄存器数量有限,不一定有空用)将它分配在寄存器中。(平时分配的一般的变量都是在内存中的)。分配在寄存器中一样的用,但是读写效率会高很多。所以register修饰的变量用在那种变量被反复高频率的使用,通过改善这个变量的访问效率可以极大的提升程序运行效率时。所以register是一种极致提升程序运行效率的手段。
(2)uboot中用到了一个register类型的变量,gd这个变量是用来存uboot的全局变量(gd就是global data)。因为这个全局变量在整个uboot中到处都被访问,所以定义成register的。
4、extern
(1)extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
(2)在b.c中引用a.c中定义的全局变量/函数有2种方法:一是在a.h中声明该函数/全局变量,然后在b.c中#include <a.h>;二是在b.c中使用extern显式声明要引用的函数/全局变量。其中第一种方法比较正式。
5、volatile
(1)volatile的字面意思:可变的、易变的。编译器在遇到volatile修饰的变量时就不会对改变量的访问进行优化。
在本线程内,当读取一个变量时,为了提高读取速度,编译器进行优化时有时会先把变量读取到一个寄存器中;以后,再读取变量值时,就直接从寄存器中读取;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以保持一致。
编译器优化有时会带来好多意想不到的问题,在次不一一举例。
一般说来,volatile关键字用在如下的几个地方。
(1)中断服务程序中修改的供其他程序检测的变量需要加volatile。
(2)多任务环境下各任务间共享的标志应该加volatile。
(3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。以STM32为例,寄存器中的数据也是时刻在变化的,我们也不想编译器优化这一点,所以在库函数中我们可以看到这样的代码。
typedef struct
{
__IO uint32_t CSR; /*!< ADC Common status register, Address offset: ADC1 base address + 0x300 */
__IO uint32_t CCR; /*!< ADC common control register, Address offset: ADC1 base address + 0x304 */
__IO uint32_t CDR; /*!< ADC common regular data register for dual
AND triple modes, Address offset: ADC1 base address + 0x308 */
} ADC_Common_TypeDef;
这是寄存器的结构体,我们查看前缀__I 和__IO到底是什么?
/* IO definitions (access restrictions to peripheral registers) */
/**
\defgroup CMSIS_glob_defs CMSIS Global Defines
<strong>IO Type Qualifiers</strong> are used
\li to specify the access to peripheral variables.
\li for automatic generation of peripheral register debug information.
*/
#ifdef __cplusplus
#define __I volatile /*!< Defines 'read only' permissions */
#else
#define __I volatile const /*!< Defines 'read only' permissions */
#endif
#define __O volatile /*!< Defines 'write only' permissions */
#define __IO volatile /*!< Defines 'read / write' permissions */
/* following defines should be used for structure members */
#define __IM volatile const /*! Defines 'read only' structure member permissions */
#define __OM volatile /*! Defines 'write only' structure member permissions */
#define __IOM volatile /*! Defines 'read / write' structure member permissions */
/*@} end of group Cortex_M4 */
6、restrict
(1)restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改,如 int *restrict ptr, ptr 指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针。
ref:
https://www.cnblogs.com/yangguang-it/p/6719261.html?utm_source=itdadao&utm_medium=referral
https://www.cnblogs.com/god-of-death/p/7852394.html
https://www.jb51.net/article/141042.htm
https://www.runoob.com/w3cnote/c-volatile-keyword.html
http://blog.chinaunix.net/uid-22197900-id-359209.html