由于刚刚学习C++不久,就按照书籍上的资料(C++ Primer Plus(第6版))自己整理加深下印象。C++内存要弄清楚3个概念,一是存储的持续性,也就是变量在程序中能够存在多长时间;二是变量的作用域,也就是从你声明变量开始,这个变量的使用范围由多广;三是变量的链接性,通常是指该变量是否可以在其它文件中被使用。
C++使用不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。
(1).自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储执行性为自动的变量 。
(2).静态存储持续性:在函数定外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
(3).线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序可放在并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
(4).动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或者程序结束为止。这种内存的存储执行性为动态,有时被称为自由存储(free store)或堆(Heap)。
1.自动存储持续性
(1)在默认情况下,在函数中声明的参数和变量的存储持续性为自动的,作用域为局部,没有链接性。
int main(){
...
int texas = 0;
return 0;
}
void oil(){
...
int texas = 0;
}
main()中声明了一个变量texas,并且oil()中也声明了一个变量名为texas,则创建了两个独立的变量(只有在定义它们的函数中才能够使用它们)。对oil()中的texas执行任何操作都不回影响main()中的texas。另外当程序开始执行这些变量所属的代码块时,将为其分配内存,当函数结束时,这些变量都会消失。
(2)自动变量和栈
由于自动变量的数目随函数的开始和结束而增减,程序必须在运行时对自动变量进行管理。常用的方法就是留出一段内存,并将其视为栈,以管理变量的增减。之所以被称为栈,是由于新数据被象征性的放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一内存单元中),当程序使用完后,将其从栈中删除。栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。程序使用两个指针来跟踪栈,一个指针指向栈底(栈的开始位置),另一个指向栈顶(下一个可用内存单元)。当函数被调用时,其自动变量将被加入到栈中,栈顶的指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数调用前的值,从而释放新变量使用的内存。
2.静态持续变量
和C一样,C++也为静态存储持续性变量提供了3中链接性:外部链接性(可在其它文件中访问),内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这3中链接性都在程序整个执行期间存在,与自动变量相比,它们的寿命更长。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理他们,编译器将分配固定的内存块来存储所有的静态变量,这些变量在程序整个运行期间一直存在。另外,如果没有显示的初始化静态变量,编译器将它们设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位置设置成0。
...
int global =1000; //static duration,external linkage
static int one_file = 50; static duration,internal linkage
int main(){
...
return 0;
}
void fun1(int n){
static int count = 0; //static duration,no linkage
int llama = 0;
}
void fun2(int n){
...
}
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
链接性为外部的变量通常简称为外部变量,它们的持续性为静态,作用域为整个文件。外部变量是在函数定义外定义的,因此对所有函数而言都是外部的。在每个使用外部变量的文件中,都必须声明它,另一方面,C++"单定义规则"(One Definition Rule,ODR),该规则指出,变量只能够有一次定义。为了满足这种需求,C++提供了两种变量声明,一种是定义声明或简称为定义,它给变量分配存储空间;另一种是引用声明或简称声明,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用关键字extern,且不进行初始化;否则声明为定义,分配存储空间。
double up; //definition because of initialization
extern int blem; //blem defined elsewhere
extern char gr = 'z'; //definition,because initialized
如果要在多个文件中使用外部变量,只需要在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其它文件中,都必需使用关键字extern声明它。
有些被称为存储说明符(storage class specifier)或者cv-限定符(cv-qualifier)的C++关键字提供了其它有关存储的信息。下面是存储说明符:
- auto(c++11中不再是说明符)
- register
- static
- extern
- thread_local(c++11新增的)
- mutable
在同一个声明中不能使用多个说明符,但thread_local除外,它可与static或extern结合使用。C++11之前,可以在声明中使用关键字auto支出变量为自动变量,但在C++11中,auto用于自动类型推断。关键字register用于在声明中指示寄存器变量,而在C++11中,它指示显示地指出变量是自动的。关键字static被用在作用域为整个文件的声明时,表示内部链接性,被用于局部声明中时,表示局部变量的存储性为静态的。关键字extern表明是引用声明,即声明引用在其它地方定义的变量。关键字thread_local指出变量的持续性与其线程的持续性相同。
(1)cv-限定符
cv表示const和volatile。关键字volatile表明,即使程序没有对内存单元进行修改,其值也有可能发生变化。例如,可以将一个指针指向某个硬件位置,其中包含了来自串行端口的时间和信息。在这种情况下,硬件(不是程序)可能修改其中的内容,或者两个程序相互影响,共享数据。该关键字的作用是为了改善编译器的优化能力。例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设两次使用值之间不会有变化。如果变量不声明为volatile,则编译器会进行这种优化,将变量设置voolatile,相当于告诉编译器,不要进行这种优化。
(2)mutable
用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改。例如:
struct _data{
char name[30];
mutable int accesses;
};
typedef struct _data Data;
const Data veep ={"xie",0,...};
strcpy(veep.name,"Guan"); //not allowed
veep.accesses++; //allowed
(3)const
在C++(但不在C语言)中,const限定符对默认存储类型稍微有影响。在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。也就是说,在C++看来,全局const定义(如下代码所示)就像使用了static说明符一样:
const int fingers =10; //same as static const int fingers=10;
int main(){
...
}
如果程序员希望某个常量的链接性为外部的,则可以使用extern关键字来覆盖默认的内部链接性:
extern const int states = 50; //definition with external linkage
在这种情况下,必须在所使用该常量的文件中使用extern关键字来声明它。
(4)函数链接性
函数的存储性为静态的。默认函数的链接性为外部,即可在文件中共享。实际上,可以在函数原型中使用关键字extern来指出函数是在另外一个文件中定义的,不过这是可选的。还可以使用关键字static将函数的链接设置为内部的,使之只能在一个文件中使用。必需在函数原型跟定义中使用该关键字:
static int private(double x);
...
static int private(double x){
...
}
(5)语言链接性
语言链接性对函数有影响。在C语言中,一个名称只对一个函数对应,因此这很容易实现。为满足内部需求,C语言编译器可能将函数名翻译为_spiff。这种方法被称为C语言链接性。但在C++中,同一个名称可能对应多个函数,必需将这些函数翻译为不同的符号名称。因此C++编译器执行名称的矫正或名称修饰,为重载函数翻译为不同的符号名称。例如,可将spiff(int)转换为_spiff_i,而将spiff(double,double)转换为_spiff_d_d。这种被称为C++语言的链接性。
链接程序寻找与C++函数调用匹配的函数时,使用的方法与C语言不同。但如果要在C++程序中使用C库中预编译的函数,将会出现什么情况呢 ?例如,假设有下面的代码:
spiff(22); //want spiff(int) from C library
它在C库文件中的符号名称为_spiff,但对于我们假设的链接性程序来说,C++查找是按照符号名称_spiff_i。为解决这个问题,可以用函数原型来指出要使用的约定:
extern "C" void spiff(int); //use C protocol for name look-up
extern void spof(int); //use c++ protocol for name look-up
extern "C++" void spaff(int) //use c++ protocol for name look-up
3.动态持续性
C++运算符new(或C函数malloc())分配的内存,这种内存被称为动态内存分配。动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配内存,在另一个函数中释放内存。与自动变量不同,动态内存不是LIFO,其分配和释放取决于new和delete在何时以何种方式使用。通常编译器使用3块内存:一块用于静态变量(可能再细分),一块用于自动变量,另外一块用于动态存储。
(1)使用new初始化
int *pi = new int(6);
double * pd = new double(99.99)
这种括号的写法也可用于有合适构造函数的类。然而,要初始化常规结构和数组,需要使用大括号的列表初始化。这要求编译器支持C++11.
struct _where{
double x;
double y;
double z;
}
typedef struct _where Where;
Where* one = new where{2.5,5.3,7.2}; //c++11
int * ar = new int[4]{2,4,6,7}; //c++11
(2)new:运算符、函数和替换函数
运算符new和new[]分别调用如下函数
void* operator new(std::size_t); //used by new
void* operator new[](std::size_t); //used by new[]
这些函数被称为分配函数(allocation function),它们位于全局名称空间,同样,也有由delete和delete[]调用的释放函数(delallocation function):
void operator delete(void *);
void operator delete[](void *);
std::size_t是一个typedef,对于合适的整型,比如下面的解释:
int* pi = new int;
将被转换为下面这样:
int* pi = new(sizeof(int));
而下面语句:
int * pa = new int[40];
将被转为下面这样:
int* pa = new (40*sizeof(int));
同样,下面语句:
delete pi;
将转换为函数调用
delete(pi);