C++语言进阶 – 内存的分区模型|内存管理

前言

内存模型就是一种语言它独特的管理者一套程序的机制,像C语言会将内存区域划分成堆、栈、静态全局变量区、常量区;而C++则分为堆、栈、自由存储区、全局/静态变量区、常量存储区;划分的目的是为了能够方便编译器的管理和运行。再比如Java和Python都有自己的一套虚拟内存管理机制和垃圾回收机制。这些操作无疑是为了方便程序员的日常开发,减少程序员对内存太多的操作。

内存分区

代码区:存放函数体的二进制代码,有操作系统管理的。

全局区:也称全局/静态存储区,存放全局变量和静态常量

常量存储区:存放常量,不允许修改(通过非正当手段也可以修改)

栈区:由编译器自动分配释放,存放函数的参数值,局部变量。

堆区:保存程序中动态分配的内存,比如C的malloc申请的内存,或者C++中new申请的内存。堆向高地址方向增长。

 

代码区

存放代码(如函数),不允许修改(类似常量存储区),但可以执行(不同于常量存储区)

  • 是编译后程序的主体,也就是程序的机器指令。
  • ​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
  • ​ 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区

全局和静态变量被分配到同一块内存中。在C语言中,未初始化的放在.bss段中,初始化的放在.data段中;在C++里则不区分了。

意:静态局部变量也存储在全局/静态存储区,作用域为定义它的函数或语句块,生命周期与程序一致。

此处具体证明看下面常量区的测试内容,有与全局区进行对比。

常量区

1常量区内存空间存储常量(包括字符串,等内容),如下面示例:

string str = "123456";

其中字符串变量str 存储在栈区,而123456作为常量存储在常量区。

网上有各种说法,有说常量会存储在全局区,也有说常量存储独立的常量区。但都没有给出任何证明,口说无凭。为了搞清楚到底是否应当有常量区的概念,我们来证明了一下。证明如下:

c++常量区与全局区证明

 

我们可以看到,无论常量在变量之前定义还是之后定义,所输出的地址跟其他变量地址不是连续的,由此说明常量与全局区不在同一内存块中。故我认为划分常量区概念更为合理

测试代码:

#include "stdlib.h"
#include <cstdio>
#include <iostream>
#include <ctime>
#include <fstream>
#include <ostream>
using namespace std;

int e = 30;
int f = 40;
const int g = 50;
const int h = 60;
static int k = 70;
static int l = 71;
const int m = 80;
const int n = 81;
int o = 30;
int p = 40;
int main()
{
    int a = 11;
    int b = 12;
    const int c = 20;
    const int d = 21;
    static int i = 66;
    static int j = 67;

    printf("Loc(全局变量 e):%d \n", &e);
    printf("Loc(全局变量 f):%d \n", &f);

    printf("Loc(全局常量 g):%d \n", &g);
    printf("Loc(全局常量 h):%d \n", &h);

    printf("Loc(全局静态变量 k):%d \n", &k);
    printf("Loc(全局静态变量 l):%d \n", &l);

    printf("Loc(全局常量 m):%d \n", &m);
    printf("Loc(全局常量 n):%d \n", &n);

    printf("Loc(全局变量 o):%d \n", &o);
    printf("Loc(全局变量 p):%d \n", &p);

    printf("Loc(局部变量 a):%d \n", &a);
    printf("Loc(局部变量 b):%d \n", &b);
    cout << "-----------------------"<<endl;
    printf("Loc(局部常量 c):%d \n", &c);
    printf("Loc(局部常量 d):%d \n", &d);
    printf("Loc(局部静态变量 i):%d \n", &i);
    printf("Loc(局部静态变量 j):%d \n", &j);
    system("pause");
    return 0;
}

 栈区

是那些编译器在需要时分配,在不需要时自动清除的存储区。存放局部变量、函数参数(返回值临时存在寄存器中)。
存放在栈中的数据只在当前函数及下一层函数中有效,一旦函数返回了,这些数据也就自动释放了。(有返回值时,会将返回值先保存到寄存器中,再销毁该被调函数的栈帧空间)

注意:不能返回局部变量的引用 。主要原因是局部变量随着函数体结束被自动销毁(函数结束会销毁该被调函数的栈帧空间),因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。

函数结束销毁栈帧

堆区

由new分配的内存块,其释放编译器不去管,由我们程序自己控制(一个new对应一个delete)。如果程序员没有释放掉,在程序结束时OS会自动回收。涉及的问题:“缓冲区溢出”、“内存泄露”。

注意:C的malloc,或者C++中new操作都是向堆中申请的内存 ,它们的数据都存放在堆中,而不是栈中。在函数结束后,堆中数据不受影响,仍然保持它的生命周期。

例如下面例子:我们使用new为数据14在堆中开辟一块空间,栈中的int *a跨区引用着该数据在堆中的内存地址。在函数结束后a变量销毁,但14这段内存块由于是存放在堆中,所以不会受到任何影响。只有当程序结束后或人为释放后该内存空间才被销毁,否则一直存放在堆中,也就容易造成我们常说的“内存泄露”问题

堆中开辟数据


2022/11/4 19:31 愚者千虑,必有⼀得。

按照我们之前所说的,被new的数据,会分配到堆区的内存块。于是乎我突然想到一个特例——数组,我们知道在C#和Java中,数组总是被分配到堆中。那么C++呢?C++的数组它既可以使用new来动态创建,也可以像这样int arr[]={1,2,3}; 直接定义初始化器来初始化元素。如果按照前面所说的 ”C的malloc,或者C++中new操作都是向堆中申请的内存 ,它们的数据都存放在堆中而不是栈中“。那这种说法对于像数组这样不同初始化方式是不是也成立呢?

我们来验证一下:

代码-不用new,直接定义初始化器:

int* assignment4_01()
{
    int arr[]={1,2,3};
    return arr;
}
void assignment4()
{
   int *arr = assignment4_01();
   cout<<arr[0]<<endl;
   cout<<arr[1]<<endl;
   cout<<arr[2]<<endl;
}

结果:报错-Segmentation fault

 

出现异常。
Segmentation fault

 

 

代码-new操作符开辟数组:

int* assignment4_01()
{
    int *arr=new int[3]{1,2,3};
    return arr;
}
void assignment4()
{
   int *arr = assignment4_01();
   cout<<arr[0]<<endl;
   cout<<arr[1]<<endl;
   cout<<arr[2]<<endl;
}

结果:

1
2
3

显而易见,的确如上面所说的。没有使用new方式构造数组的那一组结果,数组内存块在局部域结束后随之被销毁了。

 

本文案例代码:

来源:诚通网盘 | 提取码:unitymake

 

本文部分内容参考下列文章:

C/C++ 内存模型 - 林嵩 - 博客园 (cnblogs.com)

C++内存模型简述_三贝勒文子的博客

 

 

作者:Miracle
来源:麦瑞克博客
链接:https://www.unitymake.com/archives/programming-life/cpp/2269
本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议,转载请注明!
THE END
分享
打赏
海报
C++语言进阶 – 内存的分区模型|内存管理
前言 内存模型就是一种语言它独特的管理者一套程序的机制,像C语言会将内存区域划分成堆、栈、静态全局变量区、常量区;而C++则分为堆、栈、自由存储区、全局/……
<<上一篇
下一篇>>
文章目录
关闭
目 录