【c++】【基础】【primer_plus】【第九章】内存模型与作用域
前言
本章主要介绍c++的内存模型,包括存储的持续性、作用域、链接性等方面。除此之外 ,还介绍了动态内存分配的方法,及以名称空间的概念。
可以从内存分配方面了解c++程序的在单文件之外的源码组织形式。
存储持续性
存储持续性表示变量在内存中存在的时间,即生命周期,大致分为以下几种。
自动存储持续性
在执行其所属代码块或函数时创建,执行完后其内存被释放。
常见的有在函数中声明的变量,包括函数参数等,以及在代码块中被声明的变量,这些变量通常称为局部变量。
静态存储持续性
在程序整个运行过程中都存在。
常见的为在函数和代码块外定义的变量,以及在函数内使用static
定义的变量,这些变量通常称为全局变量。
线程存储持续性(c++11)
生存周期同所属的线程一样长,一般使用local_thread
声明。
动态存储持续性
使用new
分配内存的变量,会直到使用delete
释放,或者程序运行结束,才会释放其内存。
作用域
描述名称在文件(翻译单元)的可见范围,大致分为以下几种。
局部作用域 -- 只在定义它的代码块内可用,如自动变量。
全局作用域 -- 在定义位置到文件结尾都可用,如静态变量。
函数原型作用域 -- 在函数原型作用域中使用的名称只在包含参数表的括号内可用,且可省略。
类作用域 -- 在定义它的类中可用。
名称空间作用域 -- 在定义它的名称空间中可用,全局作用域为其一特例。
链接性
描述名称在不同单元之间共享的方式。
外部链接性 -- 可在文件之间共享。
内部链接性 -- 只能由其所在文件中的函数共享。
无链接性 -- 不能共享,如自动变量。
自动变量
自动变量一般都是作为局部的变量,且没有链接性。
auto
关键字原用于显式地指出变量为自动存储,c++11已经取消此用法。
局部变量可隐藏外部的或外层的变量,全局变量若不想被隐藏,可使用域解析符(::)。
可使用任何在声明时已知的表达式来初始化自动变量。
在语言底层,自动变量使用栈来简化实现。
寄存器变量
使用register
关键字声明的变量,它建议编译器使用寄存器来存储自动变量,旨在提高速度,目前已经淘汰,register
关键字目前只显式说明变量是自动的。
静态持续变量的链接性
外部链接性
静态变量直接在代码块外声明,即可获得外部链接性,有两种声明方式。
定义声明 -- 会给变量分配内存,直接声明。
引用声明 -- 不给变量分配内存,而是引用已有变量,多为其它文件中的已有变量。
其中引用声明,需要__使用extern
关键字且不初始化__,若初始化了,则还是定义声明。
一般只在一个文件中使用定义声明,而在其他文件中使用引用声明。
内部链接性
静态变量直接在代码块外声明并在前面加上static
关键字,即可获得内部链接性。
此时static
关键字表示为变量的内部链接性,变量一开始就已经是静态的了。
名称相同时,内部链接性的静态变量将隐藏常规的外部变量。
无链接性
变量在代码块内声明,并加个static
关键字,即可获得无链接性。
此时static
关键字表示为变量的存储持续性为静态,变量一开始已经是无链接性的了。
这种在不同情况下关键字的含义不同的现象,又叫做关键字重载。
限定符和说明符
c-v限定符
const
const
限定符表示不能修改的内存。
const
对全局变量的链接性有影响,它会使全局变量的链接性变为内部的。
这也是const
变量能写在头文件而没有多重定义的原因。
若想使用链接性为外部的const
变量,则可以再在前面加extern
进行定义,但其他文件使用此变量必须也要用extern
关键字进行引用声明。
1 | extern const int maxn = 100; |
volatile
volatile
用来声明易变的变量。
即使程序代码没有对内存单元进行修改,其值也可能发生变化(可能受硬件影响)。这是volatile
导致的易变性。
如果不使用volatile
变量,在两次访问之间编译器可能会认为值没有变化,从而进行优化(优化为常量)。使用volatile
变量相当于告诉编译器,不要进行这种优化。这是volatile
导致的不可优化性。
存储说明符
auto -- c++11后不再是说明符。
register -- c++11后表示为显式说明为自动变量。
static -- 内部链接性 or 静态。
extern -- 外部链接性变量的引用声明。
thread_local -- (c++11)线程的静态变量。
mutable -- 可用它来指出,即使结构体(或类)变量为
const
,其某个成员也是可以被修改的。
1 | struct node { |
函数的链接性
函数默认为外部静态的,即默认是extern
的。
可使用static
关键字将其链接性设置为内部的。但必须同时在原型和定义中使用static
。
内部静态函数将覆盖外部函数。
外部链接性的函数名矫正约定
c语言对函数名的矫正使用的约定与c++不同,可用函数原型指出其使用的约定。
1 | extern "C" void fun(int); // 使用c语言的约定 |
动态内存分配
new运算符
new
运算符用于使用堆空间给变量动态分配内存。
初始化内置的标量类型可以加括号分配内存并初始化,也适用于有合适构造函数的类。
1 | int* p = new int(5); // 初始化(*p)的值为5 |
初始化结构体或数组,使用大括号的初始化列表进行初始化(c++11)
1 | node* p = new node{1.0, 2.0}; // node为结构体 |
new
失败时,会返回空指针,并引发 std::bad-alloc
异常(第15章)
new
运算符调用如下分配函数。
1 | void* operator new(std::size_t); // new |
delete
也有相应的释放函数。
定位new运算符
new
负责在堆上找一块满足要求的内存块,但无法指定要使用的位置。
定位new
能指定要使用的位置。从而可以设置内存管理规程,处理需要通过特定地址进行访问的硬件,在特定位置创建对象等。
1 |
|
注意,不能使用delete
来释放使用定位new
分配的内存。定位new
相当于将分配内存的重任交给了程序员。
定位new
的分配函数如下。
1 | void* operator new(std::size_t, void* position); |
名称空间
概述
声明区域
声明区域指可以在其中声明的区域。
全局变量的声明区域为其声明所在的文件,局部变量为其代码块。
潜在作用域
从声明位置点开始,到其声明区域的结尾。
作用域
变量对程序而言可见的的范围。
潜在作用域可能会被嵌套的声明区域隐藏,如局部变量隐藏全局变量。
可命名的名称空间
名称空间可以是全局的, 也可以位于另一个名称空间中,但不能位于代码块中。
默认在名称空间中声明的名称的链接性为外部的(除了const
)。
全局名称空间,对应于文件级声明区域,即之前的全局变量所在空间。
名称空间是开放的,即可在不同的地方声明同一个名称空间,整个名称空间为累计起来的结果。
访问名称空间中名称的方法为,使用作用域解析运算符::
。
1 | namespace CE { |
using声明 & using编译指令
using声明和using编译指令都可以用来简化名称空间的使用。
using声明
using声明
使特定的标识符可用。
1 | using CE::x; |
其实质是将特定的名称添加到它所属的声明区域中。如果该声明区域中已经存在同名变量,则会报错。
using 编译指令
using编译指令
使该名称空间的所有名称都可用。
1 | using namespace CE; |
1 | using namespace CE; // 如果使用using CE::x;则会报错 |
其它特性
名称空间是可嵌套的。
using编译指令是可传递的。
可以给名称空间创建别名,常用于简化嵌套名称空间的使用。
1 | namespace cloud_engine = CE; |
可以使用未命名的名称空间,其作用域为整个文件,使用时不用加域解析运算符。相当于内部链接的全局静态变量(使用static关键字的全局变量)。
使用名称空间的指导规则
使用在已命名的名称空间中声明的变量,而不是使用外部全局变量。
使用在已命名的名称空间中声明的变量,而不是使用静态全局变量。
如果开发了一个函数库或类库,将其放在一个名称空间中。
仅将
using编译指令
作为一各路将旧代码转换为使用名称空间的权宜之计。不要在头文件中使用
using编译指令
,它掩盖了要让哪些名称可用。包含头文件的顺序可能影响程序的行为,
using
应放在所有的#include
之后。导入名称时,首选使用作用域解析运算符或
using声明
的方法。对于
using声明
,首选将其作用域设置为局部而不是全局。