C++内存分布

一个应用程序通常会占用以下空间,也就是内存四区:

堆区,栈区,数据区,代码区

栈区主要存储一些临时变量,临时变量包括了局部变量、返回值、参数、返回地址等,当这些变量超出了当前作用域时将会自动弹出。该栈的最大存储是有大小的,大致是8192kb,超过该大小将会造成栈溢出。

堆区指的是一个比较大的内存空间,主要用于对动态内存的分配;在程序开发中一般是开发人员进行分配与释放,若在程序结束时都未释放,系统将会自动进行回收。

数据区包含静态存储区和常量区:

静态存储区指的是主要存放全局变量、静态变量的区域,编译器编译时分配内存。将变量定义的类型前加static,则该变量存储在静态存储区
static:
1)只初始化一次
2)只有程序退出才释放

常量区,存放常量,只读状态,不可修改,程序结束后由系统释放

代码区,所有代码编译后的cpu指令会存储在这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int e = 0;
int main()
{

int a = 0;
int b = 0;
char c='0';
static int d = 0;

printf("变量a的地址是:%d\n变量b的地址是:%d\n变量c的地址是:%d\n", &a, &b, &c);

printf("静态变量d的地址是:%d\n", &d);
printf("全局变量e的地址是:%d\n", &e);

}

结果:

变量a的地址是:6487628
变量b的地址是:6487624
变量c的地址是:6487623
静态变量d的地址是:4223028
全局变量e的地址是:4223024

可以看出,a,b,c都在栈区,三个变量同属于一个栈内,所以它们地址的索引是连续性的。栈区的地址向下增长,因为栈是先进后出,栈底又是最大地址

d和e说明了全局变量与静态变量都应该存储在静态区

栈区、数据区都是使用栈结构对数据进行存储。

new/delete和malloc/free的区别:

C++ 中使用 new 和 delete 从堆中分配和释放内存,new 和 delete 是C++语言中的两个运算符,malloc/free是C语言标准库中的两个函数

new/delete 比 malloc/free ,都是用来申请动态内存的

new/delete 比 malloc/free 多做了一些事情,new 相对于 malloc 会额外的做一些初始化工作,比如调用构造函数。delete 相对于 free 多做一些清理工作,比如调用析构函数。malloc/free无法调用构造和析构的原因是,他是库函数不是运算符,不在编译器控制权限之内

malloc得到的指针无类型,new出来的指针是带有类型信息的

new/delete 比 malloc/free 要成对使用,free释放new出来的对象会导致无法析构而出错。delete释放malloc出来的内存,理论上不会出错,但是程序可读性会变差

delete和delete[]的区别

1
2
3
int *a = new int[10];
delete a;
delete[] a;

上面这种情况中的释放效果相同,原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理,在析构时,系统并不会调用析构函数。

如果 ptr 代表一个用new申请的内存返回的内存空间地址,即所谓的指针,那么:delete ptr 代表用来释放内存,且只用来释放 ptr 指向的内存。delete[] rg 用来释放rg指向的内存,!!还逐一调用数组中每个对象的destructor!!

对于像 int/char/long/int*/struct 等等简单数据类型,由于对象没有 destructor ,所以用 delete 和 delete []是一样的。但是如果是 C++ 对象数组就不同了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A
{
private:
char *m_cBuffer;
int m_nLen;

`` public:
A(){ m_cBuffer = new char[m_nLen]; }
~A() { delete [] m_cBuffer; }
};

A *a = new A[10];
delete a; //仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放 从而造成内存泄漏
delete[] a; //调用使用类对象的析构函数释放用户自己分配内存空间并且 释放了a指针指向的全部内存空间

关于 new[] 和 delete[],其中又分为两种情况:

  • (1) 为基本数据类型分配和回收空间;
  • (2) 为自定义类型分配和回收空间;

对于 (1),上面提供的程序已经证明了 delete[] 和 delete 是等同的。但是对于 (2),情况就发生了变化。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

class Babe
{
public:
Babe()
{
cout << \"Create a Babe to talk with me\" << endl;
}

~Babe()
{
cout << \"Babe don\'t Go away,listen to me\" << endl;
}
};

int main()
{
Babe* pbabe = new Babe[3];
delete pbabe;
pbabe = new Babe[3];
delete[] pbabe;
return 0;
}

结果:

1
2
3
4
5
6
7
8
9
10
Create a babe to talk with me
Create a babe to talk with me
Create a babe to talk with me
Babe don\'t go away,listen to me
Create a babe to talk with me
Create a babe to talk with me
Create a babe to talk with me
Babe don\'t go away,listen to me
Babe don\'t go away,listen to me
Babe don\'t go away,listen to me

只使用 delete 的时候只出现一个 Babe don’t go away,listen to me,而使用 delete[] 的时候出现 3 个 Babe don’t go away,listen to me。不过不管使用 delete 还是 delete[] 那三个对象的在内存中都被删除,既存储位置都标记为可写,但是使用 delete 的时候只调用了 pbabe[0] 的析构函数,而使用了 delete[] 则调用了 3 个 Babe 对象的析构函数。

反正不管怎样都是把存储空间释放了,有什么区别呢?

关键在于调用析构函数上。此程序的类没有使用操作系统的系统资源(比如:Socket、File、Thread等),所以不会造成明显恶果。如果你的类使用了操作系统资源,单纯把类的对象从内存中删除是不妥当的,因为没有调用对象的析构函数会导致系统资源不被释放,这些资源的释放必须依靠这些类的析构函数。所以,在用这些类生成对象数组的时候,用 delete[] 来释放它们才是王道。而用 delete 来释放也许不会出问题,也许后果很严重,具体要看类的代码了。

参考链接:
https://mp.weixin.qq.com/s/HAPtnOGhF2z17RhqMmUpNg