在《C++学习笔记5——C++复合类型之指针》中,在分析new的时候,总结了内存分配方法。当时,根据C++管理内存数据的方式将数据存储方式总结为自动存储、静态存储、动态存储。本篇笔记将在此基础上,深入探讨C++中内存模型的持续性、链接性和作用域。在学习完函数之后,总结这些知识有助于合理规划自己的程序结构。毕竟,稍微有点规模的程序都不可能只在一个文件或几个函数内完成。
1.什么是持续性、链接性和作用域?
作用域是空间上的概念。也即变量作用的区域。从它作用的区域大体可分为两种:局部和全局变量。
从字面意思理解,持续性是时间上的一个概念。即变量在内存中存在的时间周期,也即何时分配内存,何时释放内存。根据之前学习总结的自动存储、静态存储、动态存储方式,可以得出变量的三种持续性:
自动:在函数内声明的函数参数和变量的存储性为自动,作用域为局部,即在作用域之外自动释放内存。
静态:静态变量即用static修饰的变量,其存在于整个程序运行过程中。
动态:使用new创建的变量,需要用delete释放。
链接性表示的是多个文件(姑且以文件来作为分界,准确讲叫翻译单元)之间的变量共享方式。具体可分为三种:
无链接性:自动变量的名称没有链接性,它们不能共享。
内部链接性:链接性为内部的名称只能在一个文件中的函数共享
外部链接性:链接性为外部的名称可在文件间共享。
2.三者之间的关系
变量的持续性、链接性和作用域往往有着内在的联系。可以通过一张简单的表格来显示它:
存储描述 |
持续性 |
作用域 |
链接性 |
位置 |
自动 |
自动 |
局部 |
无链接性 |
代码块内声明的变量 |
寄存器 |
自动 |
局部 |
无链接性 |
代码块内,用register关键字 |
静态无链接性 |
静态 |
局部 |
无链接性 |
代码块内,并使用static声明 |
静态内部链接性 |
静态 |
文件全局 |
内部链接性 |
不在任何函数内,用static声明 |
静态外部链接性 |
静态 |
文件全局 |
外部链接性 |
不在任何函数内 |
动态 |
动态 |
|
|
new创建的变量 |
注意:
未被初始化的静态变量,在编译时被自动初始化为0;
只能用常量表达式来初始化静态变量。包括字面值常量,const常量,enum常量和sizeof操作符。
外部变量与自动变量同名时,局部变量与全局变量同名时,新定义的变量暂时会隐藏旧定义。
不同文件间的同名的全局静态变量相互覆盖(自己隐藏其他文件)。
静态局部变量只进行一次初始化,再次调用该函数时将不再初始化。如在add函数中定义一个static int aa=1;之后执行aa++;第一次调用时,该变量被初始化为1,aa值为2,第二次调用该函数时,不会再初始化,aa值将为2,执行完后为3.但它的作用域是在add内,在main中无法访问该变量,也即无链接性。
3.单定义规则与外部共享变量
C++规定,变量只能有一次定义。为满足这种需求,C++可以对变量进行定义和引用声明,定义即分配内存空间,引用声明不给变量分配空间,它引用已有的变量。引用声明要用extern关键字修饰,且不进行初始化。
因此,基于单定义原则,要在多个文件中共享一个变量,需要在一个文件中定义这个变量,在使用这个变量的其他文件中,用extern关键字声明它。
一种典型的应用就是常见的全局变量使用方法。我们创建一个GDfine.cpp文件,在该文件中定义一个变量int g_var,同时也可以对它进行初始化:int g_var = 1;然后定义一个GDefine.h文件,在该文件中引用声明它,即extern int g_var;这样,在其他文件使用该全局变量就可以直接用预编译头#include GDefine.h,如,在main.cpp中,就可以直接用该g_var变量了。该变量的持续性为静态,即在整个程序运行过程中都存在,链接性为外部,作用域为全局。也即在main中操作该变量,如g_var++,则g_var将在全局范围内发生变化。
全局变量可以方便多个文件甚至多个线程共享同一个变量,也成为了函数间、线程间通信的一种方式。但并不是说全局变量就可以滥用,因为易于访问的代价很大——程序不可靠。计算经验表明,程序越能避免对数据进行不必要的访问,越能保持数据的完整性,则程序越可靠。所以,通常情况下,应尽量使用局部变量,不应不加区分的使用全局变量。
4.说明符和限定符
说明符包括
auto(旧版本C++的auto表明变量是自动的,但很少有人用。在C++11 中不再是说明符,而成为了类型)
register(寄存器变量,表明该变量的使用频率较高,C++11取消了这个关键字)
static(静态变量)
extern(应用声明)
thread_local(C++11新增线程静态变量,相当于对于程序来说的静态变量)
mutable(修饰结构和类中的成员,使得结构或类为const时,其成员可修改)
cv限定符包括:
const:默认情况下全局变量的链接性是外部的,但const修饰的全局变量是内部的,也就是说在文件一开始定义const int a=10;这相当于static const int a=10;这是为了方便将常量可以放在.h中供多个文件使用,不然全局共享常量据需要extern关键字了,这显得有点繁琐。当然,这还意味着,每个文件都将有自己的一套常量,而不是共享一组常量。如果你觉得这样理解起来很困难,你也可以用extern关键字声明它,这样由于有const,其值不能改变,外部链接性对于const变量来说没有实际意义。
volatile:表明即使代码没有对某内存单元进行修改,但其值可能发生变化。即该变量是易变的,不稳定的。如硬件修改了该变量的情况。这个限定符主要用来优化编译器能力。
5.函数和语言的链接性
和变量一样,函数也有链接性。C++不允许在一个函数内定义另外一个函数,因此函数的持续性都是静态的,也即在整个程序执行期间都存在。默认情况下,函数的链接性是外部饿,也即可以在文件间共享。当然通常可以用extern来表明其来自于另一个文件中的定义。能被共享的前提就是该文件被编译。
语言也有链接性。链接程序要求每个函数都有不同的符号名。C语言通常将函数名翻译为_func,这被称为C语言链接特性。C++由于函数重载等问题,一个函数名可能对应多个函数,则翻译规则比较复杂,如void func(double, double)被翻译为_func_d_d,这种被称为C++语言链接特性。
这就带来一个问题,我们在C++程序中用C语言写了一个函数,编译器将默认用C++链接性来翻译函数名,比如我们用到了C语言中的库函数printf,链接时被翻译为_printf_i,但实际上库是_printf,这就会出现链接不成功的问题。可以用函数原型来显式的规定函数的链接性:
extern “C”void func();//C语言链接性
extern “C++” void func();//c++语言链接性
extern void func();//c++语言链接性
因此,解决上述问题的方法是,在C语言编写的函数原型前加上extern “C”即可。常见的,用C语言封装的类或DLL都将用到这个链接性声明。