在C/C++编程中,内存管理是至关重要的一个环节。程序员需要合理地分配和释放内存,以确保程序能够正常运行,避免内存泄漏和崩溃。本文将深入探讨C/C++内存管理机制,从内存分布、动态内存管理方式、new和delete的实现原理到定位new表达式,以及malloc/free和new/delete的区别,全面解析C/C++内存管理的方方面面。
更多文章, 点击关注博客主页 酷酷学!!!
正文开始
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);
}
1. 选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
globalVar在哪里?____ C staticGlobalVar在哪里?____C
staticVar在哪里?____C localVar在哪里?____A
num1 在哪里?____A
char2在哪里?____A *char2在哪里?___A
pChar3在哪里?____A *pChar3在哪里?____D
ptr1在哪里?____ A *ptr1在哪里?____B
2. 填空题:
sizeof(num1) = ____;40
sizeof(char2) = ____;5 strlen(char2) = ____;4
sizeof(pChar3) = ____;4 strlen(pChar3) = ____;4
sizeof(ptr1) = ____;4
3. sizeof 和 strlen 区别?
char2会在栈中存储, 而*char2内字符串在常量区, 但是因为是使用数组,所以会将abcd\0从常量区拷贝一份到栈区的数组
sizeof 和 strlen 区别?
【说明】
malloc/calloc/realloc/free
void Test ()
{
int* p1 = (int*) malloc(sizeof(int));
free(p1);
// 1.malloc/calloc/realloc的区别是什么?
int* p2 = (int*)calloc(4, sizeof (int));
int* p3 = (int*)realloc(p2, sizeof(int)*10);
// 这里需要free(p2)吗?
free(p3 );
//不需要,如果realloc成功, 会在p2的基础上进行扩容, 返回的是p2
//如果扩容失败,realloc会自动释放p2的空间,然后再重新找一块更大的空间,然后返回这块空间的地址.
}
malloc/calloc/realloc的区别?
3. 使用场景 malloc:适用于需要动态分配内存,但不需要初始化内存内容的场景。 calloc:适用于需要动态分配内存,并且希望内存内容初始化为0的场景。calloc的初始化特性使得它在分配内存后无需再进行额外的初始化操作。 realloc:适用于已分配的内存块大小不足以满足当前需求,或者分配了过大的内存块需要缩减的场景。realloc能够调整内存块的大小,同时尽可能保留原内存块中的数据。 4. 注意事项 使用malloc、calloc和realloc分配的内存都必须通过free函数来释放,以避免内存泄漏。 realloc在调整内存块大小时,如果成功则返回新的内存块地址(可能与原地址相同),如果失败则返回NULL,但原内存块仍然保持有效。因此,在使用realloc时,通常需要用一个临时指针来接收realloc的返回值,并在检查返回值后再决定是否更新原指针。 calloc分配的内存块会被初始化为0,这可能会带来一些额外的性能开销。如果不需要初始化内存内容,使用malloc可能更加高效。
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
对于内置类型
int main()
{
//对于内置类型,除了用法方便,和malloc没什么区别
int* p1 = new int;
int* p2 = new int[10];
//默认不初始化, 但是可以初始化
int* p3 = new int(10);//初始化为10
int* p4 = new int[10] {1, 2, 3, 4};
delete p1;
delete[] p2;
return 0;
}
对于自定义类型
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(int a1, int a2)
{
cout << "A(int a1, int a2)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
//对于自定义类型, new能够调用构造初始化,malloc不在适用
//A* p1 = new A;
//A* p2 = new A(2);//传参构造
//delete p1;//会调用析构函数
//delete p2;
A* p1 = new A[10];//连续申请10个空间
//会调用十次默认构造
//不想调用默认构造,下面是拷贝构造
A aa1(1);
A aa2(2);
A aa3(3);
A* p2 = new A[10]{ aa1,aa2,aa3 };
A* p3 = new A[10]{ 1,2,3,4,{6,7} };//也可以直接写,进行隐式类型转化
delete p1;
delete p2;
delete p3;
}
这里使用new进行内存开辟, 会自动调用它的构造函数, 使用delete也会自动调用它的析构函数
注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用 new[]和delete[],注意:匹配起来使用。
//结论:不要错配使用,一定匹配使用,否则结果是不确定
int main()
{
//对于自定义类型,delete[]需要保存需要析构的次数
// 所以会多开辟四个字节进行存储析构的次数
// 然后释放空间的时候会-4释放掉多开辟的空间
// 而delete和free不会,所以会报错
//A* p1 = new A;
A* p2 = new A[10]; //44 or 40
delete[] p2;
delete p2;
free(p2);
//对于内置类型无影响
int* p3 = new int[10]; //40
//free(p3);
return 0;
}
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是 系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 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来释放空间的。
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是: new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申 请空间失败时会抛异常,malloc会返回NULL。
new的原理
delete的原理
new T[N]的原理
delete[]的原理
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表
举个例子:
不通过new进行申请, 使用operator new, 跟malloc的用法一致, 只是前者会抛异常, 后者返回NULL, 那我们需要手动调用构造函数, 但是p1->A()这样写编译器不支持, 所以就需要用到定位new, 用法new(p1)A, 定位某个地址,进行调用其构造函数, 对于析构函数, 可以直接p1->~A()调用.
int main()
{
//A* p1 = new A;
//不想通过new进行申请,使用operator new需要手动调用构造函数
A* p1 = (A*)operator new(sizeof(A));
//p1->A(); // 编译器不支持这样显示调用构造
//new(p1)A; // 对已有空间,显示调用构造
new(p1)A(10); // 对已有空间,显示调用构造
//后面回学到的内存池调用
//A* p1 = pool.alloc(sizeof(A));
//new(p1)A(10); // 对已有空间,显示调用构造
// delete p1
p1->~A();//析构编译器支持
operator delete(p1);
// new []
A* p2 = (A*)operator new[](sizeof(A)*10);
//new(p2)A[10]{1,2,3,4}; // 对已有空间,显示调用构造
for (int i = 0; i < 10; ++i)
new(p2 + i)A(i);
// delete[]
for (int i = 0; i < 10; i++)
{
(p2 + i)->~A();
}
operator delete[](p2);
return 0;
}
malloc/free和new/delete的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地 方是:
本文深入探讨了C/C++内存管理机制,从内存分布、动态内存管理方式、new和delete的实现原理到定位new表达式,以及malloc/free和new/delete的区别,全面解析了C/C++内存管理的方方面面。
理解C/C++内存管理机制对于编写高效、安全的代码至关重要。掌握本文所述内容,能够帮助你更好地理解程序运行时的内存分配和释放过程,避免内存泄漏、越界访问等问题,进而提升代码质量。
希望本文能够对你有所帮助,如果你有任何疑问,欢迎在评论区留言讨论。
完