C++内存四大区域

Posted 捕获一只小肚皮

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++内存四大区域相关的知识,希望对你有一定的参考价值。

前言

c++在执行程序(.exe程序)时候会对内存进行划分区域,主要目的就是方便更加高效以及灵活编程.

那么,在此过程阶段,是怎样划分区域,以及各区域都是在发挥着什么作用呢? 下面博主就详细的给大家介绍介绍


所划分的内存区块有?

在执行C++程序时候,所划分出的内存区块主要有四个:

  • 代码区: 存放着程序的二进制代码,由操作系统管理
  • 全局区:存放全局变量,静态变量,以及常量(字符常量和const修饰的全局变量)
  • 栈区:存放所有的局部变量,其空间分配释放由编译器管理,当函数结束,局部变量自动被释放
  • 堆区:存放所有动态开辟的变量,其空间分配释放由程序员管理

其中在exe程序执行前只有代码区和全局区,在执行时才具有栈区与堆区


代码区解析

在代码区里面存放的是exe二进制机器指令,它(代码区)具有两个特性:

  • 共享性:即在内存里面只有一份此程序代码,而无论谁都可以使用并运行它. 好处是当被频繁使用时,节约更多空间
  • 只读性:即不允许被修改,这个很好理解.比如我们玩的王者游戏,官方设定一套皮肤100元,而当你下载此游戏并运行时候,有人给你修改了,变成一套皮肤1000元,你干吗?

全局区解析

全局区存的是全局变量,静态变量,以及常量,下面博主便演示下:

1 全局变量的地址

我们先定义几个全局变量,然后进行输出:

#include <iostream>
using namespace std;

int ga = 10; //g是global(全局)的意思
int gb = 20;
int gc = 20;

int main()
{
    cout<<"全局变量ga的地址是"<<&ga<<endl;
    cout<<"全局变量gb的地址是"<<&gb<<endl;
    cout<<"全局变量gc的地址是"<<&gc<<endl;
    return 0;
}

我们可以清晰的发现全局变量的地址都是很接近的.


2 静态变量的地址

我们定义几个静态变量,然后输出地址:

#include <iostream>
using namespace std;

int ga = 10; //g是global(全局)的意思
int gb = 20;
int gc = 20;

int main()
{
    static int sa = 10;
    static int sb = 20;
    static int sc = 20;
    
    cout << "全局变量ga的地址是" << &ga << endl;
    cout << "全局变量gb的地址是" << &gb << endl;
    cout << "全局变量gc的地址是" << &gc << endl;

    cout << "静态变量sa的地址是" << &sa << endl;
    cout << "静态变量sb的地址是" << &sb << endl;
    cout << "静态变量sc的地址是" << &sc << endl;
    return 0;
}

我们发现静态变量与全局变量的地址都是非常接近的,原因就是他们都存在全局区域


3 常量(字符常量及const全局常量)

一样的道理,首先创建常量,然后输出地址

#include <iostream>
using namespace std;

int ga = 10; //g是global(全局)的意思
int gb = 20;
int gc = 20;

const int cga = 10; //c是const
const int cgb = 10;
const int cgc = 10; 


int main()
{
    static int sa = 10;
    static int sb = 20;
    static int sc = 20;
    cout << "全局变量ga的地址是" << &ga << endl;
    cout << "全局变量gb的地址是" << &gb << endl;
    cout << "全局变量gc的地址是" << &gc << endl;
    cout << endl;
    cout << "静态变量sa的地址是" << &sa << endl;
    cout << "静态变量sb的地址是" << &sb << endl;
    cout << "静态变量sc的地址是" << &sc << endl;
    cout << endl;
    cout << "字符常量1的地址是" << &"123"<<endl;
    cout << "字符常量1的地址是" << &"124"<<endl;
    cout << "字符常量1的地址是" << &"125"<<endl;
    cout << endl;
    cout << "const修饰的全局变量cga地址是" << &cga<<endl;
    cout << "const修饰的全局变量cgb地址是" << &cgb<<endl;
    cout << "const修饰的全局变量cgc地址是" << &cgc<<endl;
    return 0;
}

我们仍然可以清晰的看到全局变量的地址都是在一定的区域段的(不要说相同段只有005B哦,这是16进制,换算成10进制,这个区段很大的)


栈区解析

栈区存放的是局部变量,现在我们用一些全局变量和局部变量进行比较

1 普通局部变量

#include <iostream>
using namespace std;

int ga = 10; //g是global(全局)的意思
int gb = 20;
int gc = 20;

int main()
{
    int la = 10; //l是local(局部的意思)
	int lb = 20;
	int lc = 20;
    
    cout << "全局变量ga的地址是" << &ga << endl;
    cout << "全局变量gb的地址是" << &gb << endl;
    cout << "全局变量gc的地址是" << &gc << endl;
    cout << endl;
	cout << "局部变量la的地址是" << &la << endl;
	cout << "局部变量lb的地址是" << &lb << endl;
	cout << "局部变量lc的地址是" << &lc << endl;
    return 0;
}

可以发现局部变量前面区段的地址和全局变量差距特别大,这就很好的说明了全局区栈区是两个不同区域


2 const修饰局部变量

我们用全局变量,全局const,局部变量,局部const进行对比

#include <iostream>
using namespace std;

int ga = 10; //g是global(全局)的意思
int gb = 20;
int gc = 20;

const int cga = 10; //c是const
const int cgb = 10;
const int cgc = 10;

int main()
{
    int a = 10;
    int b = 10;
    int c = 10;
    const int ca = 10;
    const int cb = 20;
    const int cc = 30;
    cout << "全局变量ga的地址是" << &ga << endl;
    cout << "全局变量gb的地址是" << &gb << endl;
    cout << "全局变量gc的地址是" << &gc << endl;
    cout << endl;
    cout << "const修饰的全局变量cga地址是(全局常量)" << &cga << endl;
    cout << "const修饰的全局变量cgb地址是(全局常量)" << &cgb << endl;
    cout << "const修饰的全局变量cgc地址是(全局常量)" << &cgc << endl;
    cout << endl;
    cout << "局部变量a的地址是" << &a << endl;
    cout << "局部变量b的地址是" << &b << endl;
    cout << "局部变量c的地址是" << &c << endl;
    cout << endl;
    cout << "const修饰的局部变量ca的地址是(局部常量)" << &ca << endl;
    cout << "const修饰的局部变量cb的地址是(局部常量)" << &cb << endl;
    cout << "const修饰的局部变量cc的地址是(局部常量)" << &cc << endl;
    return 0;
}

可以发现全局const常量和局部const常量他们的所属区是不一样的,局部const常量是所属于栈区.


3 栈区注意事项

栈区里面的变量不可返回地址,因为在func函数结束时候a的空间就被释放了,里面存的值就没有了,不信我们看下面:

#include <iostream>
using namespace std;

int* func()
{
    int a = 10;
    return &a;
}

int main()
{
	int* p = func();
    cout<<"*p的值是"<<endl;
    return 0;
}

咦???,博主被打脸了哎,怎么还是可以访问到值呢?并且是正确的10

真的是这样吗?我们再调用几次试试?

发现是不是只有第一次调用才是正确的?其实之所以第一次调用对了,是因为编译器(VS2019)害怕你误用,特地给你保留了一次结果,哎~~,编译器为了我们这些憨憨可真是操碎了心.


堆区解析

堆区的开辟释放由程序员自己执行,开辟一般用new,释放一般用delete

#include <iostream>
using namespace std;

int* func()
{
    int* p = new int(10);   //看不懂这里的,下面有介绍new用法
    return p;
}

int main()
{
	int* p = func();
    cout<<"func的值是"<<*p<<endl;
    cout<<"func的值是"<<*p<<endl;
    cout<<"func的值是"<<*p<<endl;
    delete p;
    return 0;
}

我们发现堆区就完全不受影响,其值仍然是堆区的值10


new的用法

1 开辟单个堆区元素

语法:

  • 开辟:type* name = new type(content);type是元素类型,content是元素内容,name是变量名
  • 释放: delete name
#include <iostream>
using namespace std;

int main()
{
    int* p1 = new int(10);         //开辟整型元素,存10进去
    float* p2 = new float(20.12);  //开辟单精度浮点,存20.12进去
    char* p3 = new char('w');      //开辟字符元素,存'w'进去
    
    cout << "整型元素的值是" << *p1 <<endl;
    cout << "浮点元素的值是" << *p2 <<endl;
    cout << "字符元素的值是" << *p3 <<endl;
    delete p1;
    delete p2;
    delete p3;
    return 0;
}

2 开辟数组

语法:

  • type* name = new type[size]; name是变量名,type是类型,size是数组空间数量
  • 释放: delete []name 必须有一个[ ]哦~~~
#include <iostream>
using namespace std;

int main()
{
    int* p1 = new int[10];    //开辟整型数组,10个元素
    char* p2 = new char[10];  //开辟字符数组,10个元素
    for (int i = 0; i < 10; i++) //整形数组赋值
        p1[i] = i + 10;
    for (int i = 0; i < 10; i++)//字符数组赋值
        p2[i] = i + 65;


    for (int i = 0; i < 10; i++) //打印
        cout << p1[i] << ' ';

    cout << endl;
    
    for (int i = 0; i < 10; i++)
        cout << p2[i] << ' ';
    delete []p1;
    delete []p2;
    return 0;
}

以上是关于C++内存四大区域的主要内容,如果未能解决你的问题,请参考以下文章

C++:四大内存分区

面试官常问系列:Java虚拟机内存四大问题,都在这了!

GC四大算法

C++的程序内存模型

C++核心编程之-内存分区模型

C++内存重叠