C++语言内存管理是指:对系统的分配、创建、使用这一系列操作。在内存管理中,由于是操作系统内存,使用不当会造成很麻烦的后果。本文将从系统内存的分配、创建出发,并且结合例子来说明内存管理不当会造成的结果以及解决方案。
在计算机中,每个应用程序之间的内存是相互独立的,通常情况下应用程序A并不能访问应用程序B,当然我们可以使用一种特殊技术,让他们可以互相访问,但这个不是本文的重点。比如在计算机中,一个视频播放程序与一个浏览器程序,他们的内存并不能访问,每个程序所拥有的内存是分区进行管理的。
在计算机系统中,运行程序A将会在内存中开启程序A的内存区域1,运行程序B将会在内存中开辟程序B的内存区域2,内存1和内存2之间逻辑分离。
在程序A开辟的内存区域1会被分为几个区域,这就是内存四区,内存四区分为栈区、堆区、数据区和代码区。
栈区:指的是存储一些临时变量的区域,临时变量包括了局部变量、返回值、参数、返回地址等,当这些变量超出了当前作用域时将会自动弹出。该栈的最大存储是有大小的且固定的,超过该代销将会造成栈溢出。
堆区:是一个比较大的内存空间,主要用于对动态内存的分配,在程序开发中一般是开发人员进行分配和释放,若在程序结束时都还未释放,系统将自动进行回收。
数据区:主要存放全局变量、常量、静态变量,数据区又可以划分为:全局区和静态区,全局变量和静态变量将会存放到该区域。
代码区:比较简单,主要存储可执行代码,该区域的属性是只读的。
栈区
由于栈区和堆区的底层结构比较直观的表现,我们在此使用代码只演示这两个概念来查看代码观察栈区的内存地址分配情况。
// main.cpp
#include
int main() {
int a = 0;
int b = 0;
int c = 0;
printf("变量a的地址: %d", &a);
printf("\n变量b的地址: %d", &b);
printf("\n变量c的地址: %d", &c);
// 打印结果
}
为什么它们之间的地址不是增加而是减少呢?那是因为栈区的一种数据存储结构为先进后出,如图:
思考:在分配内存的时候,为什么 定义int 类型,内存大小相差4字节,而定义char类型内存相差1字节
要回答上面这个问题,我们首先要理解的就是:“跳跃力”。
对于一个指针 而言,如果前面规定了它的类型,那就相当于决定了它的“跳跃力”。“跳跃力”就比如说上图中 int类型跳了 4个字节,char类型跳了1字节,double类型跳了8字节。
数据区
我们定义一个全局变量和 静态变量看看效果
#include
int e = 0;
int main() {
static int f = 0;
printf("\n var e address: %d", &e);
printf("\nvar f address: %d", &f);
}
// 打印结果
var e address: 8430692
var f address: 8430696
从这个例子可以看出,数据区的地址不是栈结构,它是由低向高分配的,这里还是和栈区有点区别的。
以上例子其实也说明了栈区的特性,就是容量具有固定大小,超过最大容量将会造成栈溢出
#include
int main() {
char char_arr[1024 * 1000000000];
char_arr[0] = 'a';
}
// 编译之后直接会报错
堆并没有栈一样的结构,也没有栈一样的先进后出。我们需要人为的对内存进行分配使用。
#include
#include
int main() {
//定义一个指针:指向一个大容量的内存地址
char* p1 = (char*)malloc(1024 * 1000);
// 把字符串"here is heap"赋值到指针 p1指向的空间中
std::string s = "here is heap";
strcpy_s(p1, 1024,s.c_str());
//打印内存空间的地址
printf("p1 storage address: %d", p1);
printf("\np1 storage: %s", p1);
}
//打印结果
p1 storage address: 19443776
p1 storage: here is heap
多说一点
char *p=new char[10];
p="hello";
delete p[];//此步释放时将报错❌这是因为:在C++中,字符串常量"hello"被保存在常量存储区,而p="hello"操作是改变了指针的指向,使得指针p指向了常量存储区的"hello",造成了初始在堆上开辟的内存泄露,而delete无法释放常量存储区的内存,导致出错。
在 C 语言(不是 C++)中,malloc 和 free 是系统提供的函数,成对使用,用于从堆中分配和释放内存。malloc 的全称是 memory allocation 译为“动态内存分配”。
在开辟堆空间时我们使用的函数为 malloc,malloc 在 C 语言中是用于申请内存空间,malloc 函数的原型如下:
void *malloc(size_t size);
释放空间使用free函数原型如下:
void free(void *ptr);
free函数的返回值是void ,没有返回值,接收的参数为使用malloc分配的内存空间指针
下面看一下一个完整的堆内存申请与释放的例子:
// main.cpp
int main(){
// 下面是一个完整申请,释放内存的例子,可以参考一下。
int n, *p, i;
printf("请输入一个任意长度的数字来分配空间::");
scanf_s("%d", &n);
p = (int*)malloc(n * sizeof(int));
if (p ==NULL)
{
printf("内存申请失败");
return 0;
}
else
{
printf("内存申请成功");
}
// 使用 memset对内存空间进行填充
memset(p, 0, n * sizeof(int));
// 查看刚刚填充的内容
for (int i = 0; i < n; i++)
{
printf("\n%d", p[i]);
}
// 释放空间
free(p);
p = NULL;
return 0;
}
分析上述代码可知:
// main.cpp
int main(){
char* p1;
p1 = (char *)malloc(100);
printf("内存开始被泄漏了");
return 0;
}
new 一般使用格式:
指针变量名 = new 类型标识符
指针变量名 = new 类型标识符(初始值)
指针变量名 = new类型标识符【内存单元个数】
// main.cpp
char * getMemory(unsigned long size) {
char *p = new char[size];
return p;
}
int main(){
try {
// 有可能会发生异常
char *p = getMemory(1000000);
// ...........
delete[] p;
}
catch(const std::bad_alloc &ex) {
cout << ex.what();
}
}
plain new 在分配失败的情况下,抛出异常 std::bad_alloc 而不是返回 NULL,
因此通过判断返回值是否为 NULL 是徒劳的。
C++中对new申请的内存的释放方式有 delete和delete[]两种方式,那么这两种方式有什么区别了 ?
为基本数据类型分配和回收空间;
为自定义类型分配和回收空间;
现在我们针对基本数据类型分析 delete/delete[]
int *a = new int[10];
delete a;
delete[] a;
针对Class类型,他们的差异体现在
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[100];
// 仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放 从而造成内存泄漏.
delete a;
//调用使用类对象的析构函数释放用户自己分配内存空间并且 释放了a指针指向的全部内存空间
delete a[]
总结:
如果ptr代表一个用new 申请的内存返回的内存空间地址,即所谓的指针,那么:
delete ptr 代表用来释放内存,且只用来释放ptr指向的内存。
delete[] ptr 用来释放ptr指向内存,同时还逐一调用数组每个对象的 destructor 。
对于像 int/ char/ long/int */ struct 等简单的数据类型,由于对象没有 析构函数(destructor)所以使用 delete或者 delete[] 是一样的,但是如果是C++对象数组就不同了。
下面我们来证看一个例子:学习delete 和 delete[] 使用方法
#include
#include
class Babe
{
public:
Babe()
{
std::cout<< "Create a Babe" <
可以看到:
析构函数特点:
未完待续:看完这篇你还能不懂C语言/C++内存管理?
把内存管理理解好,C语言真的不难学。今天带你“攻破”内存管理