我们先来看下面的一段代码和相关问题
int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
static int staticVar = 1;
int localVar = 1;
int num1[10] = { 1, 2, 3, 4 };
char char2[] = "abcd";
const char* pChar3 = "abcd";
int* ptr1 = (int*)malloc(sizeof(int) * 4);
int* ptr2 = (int*)calloc(4, sizeof(int));
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
free(ptr1);
free(ptr3);
}
如下图所示:
🥳🥳sizeof与strlen区别?
sizeof
和strlen
是C语言中的两个不同的操作符/函数,它们的功能和用法有所不同:
sizeof
是一个操作符(也可以看作是编译时的关键字),用于获取数据类型或变量在内存中所占的字节数。它可以用于获取数组、结构体、指针等数据类型的大小。strlen
是一个库函数(需要包含<string.h>
头文件),用于计算以null结尾的字符串的长度(不包括null字符)。它的参数是一个字符数组(字符串),它会返回字符串的有效长度(以字节为单位)。总结:
sizeof
用于获取数据类型或变量占用的字节数。strlen
用于获取以null结尾的字符串的有效长度(不包括null字符)。详情可查看土土之前的博客——C语言动态内存管理函数
C++兼容C语言,所以在C++中也可以使用C语言的动态内存管理函数来开辟和释放空间;
这里可以思考两个问题:
1.
malloc
函数用于分配指定字节数的内存空间;calloc
函数用于分配指定数量、指定大小的连续内存空间,并可将分配的内存空间进行初始化;realloc
函数用于重新分配已经分配的内存空间的大小。2.
malloc
函数用于在堆上动态分配内存空间,其实现原理可以简要概括为以下步骤:
malloc
函数会接收用户请求的内存大小,并计算需要分配的总内存大小(包括额外的管理信息)。
malloc
函数会搜索内存堆的空闲链表(free list)来找到适合大小的空闲块。空闲链表是一组已经被释放的内存块,被组织成链表结构以便快速查找。
malloc
函数会将该空闲块从空闲链表中移除,并返回该块的起始地址给用户。
malloc
函数会请求操作系统分配更多的内存空间。操作系统会分配一块更大的内存区域,并将其划分成一个新的空闲块,返回给malloc
函数。
malloc
函数会在块的开头保存管理信息(如块大小等),并返回给用户余下的部分。
需要注意的是,C标准库中的malloc
函数的具体实现可能因编译器和操作系统的不同而有所差异,上述步骤仅为一种常见的实现方式。
我们发现每次使用malloc开辟空间都需要自己计算开辟空间的大小,并且还要使用类型强转:
int* p1 = (int*)malloc(sizeof(int));
比较麻烦,而C++中开辟空间的方法就简便很多;
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new
和delete
操作符进行动态内存管理。
void Test()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[10];
//释放空间
delete ptr4;
delete ptr5;
delete[] ptr6;
}
这里不用计算开辟空间的大小也不需要进行类型的强制转换;
如下图所示:
注意:申请和释放单个元素的空间,使用
new
和delete
操作符,申请和释放连续的空间,使用new[]
和delete[]
,要注意匹配起来使用。
✨对于开辟多个元素的空间初始化:
int* ptr7 = new int[10]{1,2,3,4,5};
delete[] ptr7;
这里可以全部初始化,也可以部分初始化(剩下的会自动初始化为0);
例如下面的自定义类型:
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
A* p1 = (A*)malloc(sizeof(A));
没有办法对它进行初始化,无法自动调用该对象的构造函数进行初始化;A* p2 = new A;
可以自动调用它的构造函数进行初始化;delete p2;
也会自动调用它的析构函数并释放空间;如下图所示:
🥳🥳new/delete 和 malloc/free最大区别是 new/delete对于自定义类型除了开空间还会调用构造函数和析构函数;而对于内置类型是几乎是一样的;
✨当然对于开辟多个对象也会自动多次调用构造函数和析构函数,例如:
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = new A[10]; //自动调用构造函数初始化
A* p2 = new A[10]{ A(0),A(1),A(2),A(3) }; //使用匿名对象初始化
A* p3 = new A[10]{0,1,2,3,4}; //使用隐式类型转换初始化
delete[] p1;
delete[] p2;
delete[] p3;
return 0;
}
结果如下:
可以看出自动调用了构造函数与析构函数
new
和delete
是用户进行动态内存申请和释放的操作符,operator new
和operator delete
是系统提供的全局函数,new
在底层调用operator new
全局函数来申请空间,delete
在底层通过operator delete
全局函数来释放空间。
以下是库中operator new
和operator delete
的实现(看一下就行):
/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,
尝试执行空 间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
*/
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
// try to allocate size bytes
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// report no memory
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0)); if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK); /* block other threads */
__TRY
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK); /* release other threads */
__END_TRY_FINALLY
return;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常(这个我们之后学习)。operator delete 最终是通过free来释放空间的。
operator new
和operator delete
的功能和malloc
和free
一样,没什么区别,operator new
和operator delete
是为了实现对new
和delete
的封装;
如果申请的是内置类型的空间,new和malloc,delete和free基本类似;不同的地方是:new/delete
申请和释放的是单个元素的空间,new[]
和delete[]
申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
✨new的原理:
✨delete的原理:
✨new T[N]的原理:
✨delete[]的原理:
malloc/free和new/delete的共同点是:
都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
✨什么是内存泄漏:
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
✨内存泄漏的危害:
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
例如:
void MemoryLeaks()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
C/C++程序中一般我们关心两种方面的内存泄漏: ✨堆内存泄漏(Heap leak):
堆内存指的是程序执行中依据须要分配通过
malloc / calloc / realloc / new
等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
✨系统资源泄漏:
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
C++内存管理是指在C++程序中对内存的使用和释放进行有效管理的过程。由于C++是一种底层语言,在开发过程中需要手动分配和释放内存,这就要求程序员负责管理动态分配的内存,确保内存的正确分配和释放,避免内存泄漏和悬挂指针等问题。
☑️C++使用的内存管理方式:通过new和delete操作符进行动态内存管理。相较于C语言得maloc,new不用计算开辟空间的大小也不需要进行类型的强制转换,还可以进行初始化;此外new和delete对于自定义类型除了开辟空间和释放空间,还可以自动调用它得构造函数和析构函数;通过合理的内存管理,可以提高程序的性能和稳定性,减少内存相关问题的发生。 以上就是今天C++内存管理的所有内容啦~ 完结撒花 ~🥳🎉🎉