C++ 内存模型

一、单独编译

1.1、常见程序结构

C++鼓励程序员将组件函数放到独立的文件中,下面是一种常用的组织文件的策略,如下

  • 头文件:包含结构声明和使用这些结构的函数的原型
  • 源代码文件:包含与结构有关的函数的代码
  • 源代码文件:包含调用与结构相关的函数的代码

下面列出了头文件中常包含的内容,如下:

  • 函数原型
  • 使用#define或const定义的符号常量
  • 结构声明
  • 类声明
  • 模板声明
  • 内联函数

1.2、引用自定义头文件

如果文件名包含在尖括号中,则C++编译器将在存储标准头文件的主机系统的文件系统中查找;但如果头文件包含在双引号中,则编译器将首先查找当前的工作目录或源代码目录。如果没有在那里找到头文件,则将在标准位置查找。因此,在包含自己的头文件时,应使用引号而不是尖括号。

二、存储持续性、作用域和链接

C++使用三种(在C++11中是四种)不同的方案来存储数据,这些不同方案的区别就在于数据保留在内存的时间。

  • 自动存储持续性:在函定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有2种存储持续性为自动的变量。
  • 静态存储持续性:在函数定义外定义的变量和使用static关键字定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
  • 线程存储持续性(C++ 11):如果变量是使用thread_local声明的,则其生命周期与所属的线程一样长。
  • 动态存储持续性:使用new运算符分配的内存将一直存在,直到使用delete释放申请的内存或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储或堆。

2.1、自动存储持续性

在函数声明中的函数参数和变量的存储持续性为自动,作用域为局部,没有连接性。如下:

void display(int x, int y) {
    // 局部变量,存储持续性为自动、作用域为局部、没有连接性
    int z = x + y;
    cout << "x + y = " << z << endl;
}

2.2、静态存储持续性

和C语言一样,C++也为静态存储持续性变量提供了3种连接性:外部连接性(可在其他文件中访问)、内部连接性(只能在当前文件中访问)和无连接性(只能在当前函数或代码块中访问)。这3种连接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长,编译器会分配固定的内存块来存储所有的静态变量。此外,如果没有显示初始化静态变量,编译器把它设置为0。如下:

// 全局变量,连接性为外部,可以在多个文件间共享
int g_count = 10;

// 全局静态变量,连接性为内存,只能在当前文件中访问
static int one_file = 20;

double display(int x, int y) {
    // 局部静态变量,没有连接性,只能在当前代码块内访问
    static int local_count = 50;

    return x + y;
}

int main()
{
    double x = 2.0;
    double y = 3.0;
    cout << display(x, y) << endl;
	return 0;
}

注意:如果初始化静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。

可使用外部变量在多文件程序的不同部分之间共享数据,可使用连接性为内部的静态变量在同一个文件的多个函数之间共享数据。此外,如果将作用域为整个文件的变量变为静态的,就不必担心其名称与其它文件的作用域为整个文件的变量发生冲突。

2.3、链接

c 语言中有3种链接属性: 外部(external), 内部(internal),无设置(none)

  • 内部链接:如果一个名称对编译单元来说是局部的,在链接的时候其他编译单元无法链接到它且不会与其他编译单元中的同样名称相冲突(例如:被关键字staticinlineconst标识)
  • 外部链接:如果一个名称对编译单元来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互(例如:全局变量、函数)

三、单定义规则

C++有单定义规则(One Definition Rule,ODR),该规则指出变量只能有一次定义。为了满足这种需求,C++提供了两种变量声明。一种是定义声明或简称为定义,它给变量分配存储空间;另一种是引用声明或简称为声明,它不给变量分配存储空间,因为它引用已有的变量。引用声明使用extern关键字,且不能进行初始化;否则声明为定义,导致分配存储空间。如下:

// file01.cpp
extern int g_count = 20; // 定义变量g_count

// file02.cpp
extern int g_count;  // 引用全局变量g_count

如果需要在多个文件中使用外部变量,只需要在一个文件中包含该变量的定义,但是在使用该变量的其他文件中,都必须使用extern关键字声明它。

四、存储说明符与限定符

4.1、存储说明符

有些被称为存储说明符限定符的C++关键字提供了其他有关存储的信息,下面是存储说明符,如下:

  • auto(在C++11不再是说明符)
  • register
  • static
  • extern
  • thread_local(C++11新增的)
  • mutable

4.2、cv限定符(cv表示const和volatile)

最常用的cv限定符是const,它对默认存储类型有影响。在默认情况下全局变量的连接性为外部的,但const全局变量的连接性为内部的。如下:

// const全局变量的连接性为内部的
const int g_count = 10;

为什么头文件可以放置const定义?

C++修改了常量类型的规则,让程序员更轻松。例如:假设将一组常量放到头文件中,并在同一个程序的多个文件中使用该头文件。那么,预处理器将头文件的内容包含到每个源文件后,所有的源文件都包含类似下面这样的定义,如下:

const int g_god = 10;
const int g_cat = 20;
const char *g_name = "jack";

如果全局const声明的链接性像常规变量那样是外部的,将违反单定义规则。也就是说,只有一个文件包含前面的声明,其它文件必须使用extern关键字来提供引用声明。内部链接性意味着,每个文件都有自己的一组常量,而不是所有的文件共享一组常量,每个定义都是文件私有的,这就是能够将常量定义放到头文件的原因。

五、函数与链接性

和变量一样函数也有链接性,和C语言一样C++不允许在一个函数内部定义另一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间都一直存在。默认情况下函数的链接性是外部的,即可以在文件间共享。可以使用extern关键字来指出函数是在另外一个文件中定义的,还可以使用static关键字将函数的链接性声明为内部的,如下:

// 函数链接性默认是外部的
void display(int x, int y) {
    cout << x << y << endl;
}

单定义规则也适用于函数,因此,对于每个非内联函数,程序只能包含一个定义。内联函数不受该规则的约束,这允许程序员能将内联函数的定义放在头文件中。这样,包含了头文件的每个文件都有内联函数的定义。然而,C++要求同一个函数的所有内联定义都必须相同。

六,程序内存分配方式

内存分配大致可以分成5块,如下:

  • 栈区:栈,是由编译器在需要时分配,在不需要的时候就自动清除的存储区。里面的变量通常是局部变量与函数参数
  • 堆区:一般由程序员动态分配与释放,若程序员不释放,在程序运行结束时,可能被系统回收。注意:它与数据结构中的堆是两码事
  • 全局区(静态区 static):全局变量与静态变量被分配到同一块内存中,程序运行结束后由系统进行释放
  • 常量存储区:常量字符串就存储在这里,不允许修改,程序运行结束时由系统释放
  • 程序代码区:存放函数体的二进制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值