让我们先来看看这段代码:
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);
}
你知道上面代码中定义的变量分别存储在内存中的哪些部分吗?
说明一下:
非静态局部变量、函数参数和返回值
等等,栈是向下增长
的。动态内存分配
,堆是向上增长
的。我们使用malloc
动态内存申请的空间在堆上。包括我们一会儿讲到的new
也是如此。存储全局变量
和静态数据
。存储可执行的代码
和只读常量
。malloc
、realloc
、calloc
和 free
是C语言中用于动态内存管理的标准库函数,它们定义在<stdlib.h>
头文件中。这些函数允许程序在运行时根据需要分配和释放内存,而不是在编译时静态地分配内存。这对于处理未知大小的数据或需要动态增长的数据结构(如链表、树等)特别有用。
malloc
(Memory Allocation)函数用于动态分配一块指定大小的内存区域。其原型为:
void* malloc(size_t size);
size
参数指定了要分配的字节数。NULL
。使用 malloc
分配的内存区域是未初始化的,其内容是未定义的。
realloc
(Re-Allocation)函数用于重新调整之前通过 malloc
、calloc
或 realloc
分配的内存区域的大小。其原型为:
void* realloc(void* ptr, size_t size);
ptr
是指向要调整大小的内存区域的指针。如果 ptr
是 NULL
,则 realloc
的行为类似于 malloc
,分配一块新的内存区域。size
是新的大小。NULL
,但原内存区域不会被释放。calloc
(Contiguous Allocation)函数也用于动态分配内存,但它还会将分配的内存区域初始化为零。其原型为:
void* calloc(size_t num, size_t size);
num
指定了要分配的元素数量。size
指定了每个元素的大小(以字节为单位)。calloc
分配的内存总大小是 num * size
。NULL
。free
函数用于释放之前通过 malloc
、calloc
或 realloc
分配的内存区域。其原型为:
void free(void* ptr);
ptr
是指向要释放的内存区域的指针。ptr
指针就成为悬垂指针(dangling pointer),不应再被使用。总的来说,malloc
、realloc
、calloc
和 free
提供了在C语言中进行动态内存管理的核心功能,允许程序在运行时灵活地管理内存资源。
面试题:malloc、realloc和calloc有什么区别? malloc:动态申请空间,但不对空间进行初始化 realloc:对申请过的内存空间进行扩容处理。 calloc:申请空间的同时进行初始化处理,calloc=malloc+memset。
C语言的动态内存申请函数对于C++依旧可以使用。但也引入了新的动态内存申请方式:new、delete。
注意:malloc、realloc和calloc属于函数,但是new和delete属于操作符
new
操作符用于在堆(heap)上动态分配内存,并调用对象的构造函数(如果有的话)。其基本语法有两种形式:
为单个对象分配内存:
TypeName* pointer = new TypeName(initializer);
这里,TypeName
是要创建的对象类型,initializer
是传递给对象构造函数的参数(如果构造函数需要的话;如果构造函数没有参数或对象类型是基本数据类型,则可以省略)。pointer
是一个指向新创建对象的指针。
为对象数组分配内存:
TypeName* array = new TypeName[arraySize];
这里,TypeName
是数组元素的类型,arraySize
是数组中元素的数量。array
是一个指向数组第一个元素的指针。注意,对于数组,不会调用构造函数来初始化每个元素(除非元素类型是类类型且该类提供了默认构造函数),而是进行默认初始化(对于类类型,调用默认构造函数;对于内置类型,不进行初始化)。
delete
操作符用于释放之前通过 new
分配的内存,并调用对象的析构函数(如果有的话)。其语法也有两种形式,对应于 new
的两种用法:
释放单个对象:
delete pointer;
这里,pointer
是指向之前通过 new
分配的内存的指针。使用 delete
后,pointer
变成了悬垂指针,不应再被使用。
释放对象数组:
delete[] array;
这里,array
是指向之前通过 new[]
分配的内存的指针。注意,对于数组,必须使用 delete[]
而不是 delete
来释放内存,以确保为每个元素调用析构函数(如果元素类型是类类型的话)。
new
分配的内存必须使用 delete
(或 delete[]
)来释放,以避免内存泄漏。new
会自动调用构造函数,delete
会自动调用析构函数。这是 new
/delete
与 malloc
/free
的一个重要区别。new
表达式失败(例如,由于内存不足),它会抛出 std::bad_alloc
异常(在 <new>
头文件中定义)。因此,在使用 new
时,可能需要考虑异常处理。int
、float
等),new
和 delete
主要用于分配和释放内存,不会调用任何特殊的构造函数或析构函数。然而,对于类类型,它们的行为更加复杂,因为它们涉及到对象的构造和析构。#include<iostream>
using namespace std;
int main()
{
int* p1 = new int(10);//申请一个空间
int* p2 = new int[10];//申请一个数值
delete p1;//释放一个空间
delete []p2;//释放一个数组
}
思考一下:既然已经有了malloc等函数,为什么还要设计出new这些操作符呢?new相对于malloc有哪些优势呢?
让我们先来看看这段代码:
class A
{
public:
A()
{
_a = 1;
cout << "A()" << endl;
}
public:
int _a;
};
int main()
{
A* a1 = (A*)malloc(sizeof(A));
cout << "--------------------------------------" << endl;
A* a2 = new A;
}
运行一下,我们会发现:
说明一下: 对于自定义类型的对象,例如类对象,new对象的同时会调用构造函数对对象进行构造,delete对象的同时会调用析构函数对对象进行析构。
new和delete是用户进行动态内存申请和释放的操作符,
operator new 和operator
delete是系统提供的全局函数
,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。operator new和operator delete在用法上和malloc和free完全一样,都会在堆上申请空间
用法如下:
int* p = (int*)operator new(sizeof(int) * 10);//申请10个int类型大小的空间
operator delete (p);//对申请的空间进行释放
其在用法上等价于:
int* q = (int*)malloc(sizeof(int) * 10);
free(p);
尽管operator new和malloc在用法和作用上非常相似。但是仍然有不同之处?
不同之处有如下:
处理错误的方式不同,让我们看看如下的代码:
总结一下: 在申请失败的情况下,malloc返回0,operator new抛异常。
malloc VS operator new VS new
什么是内存泄露?内存泄露有什么⚠️?
内存泄露的分类
C/C++程序中一般我们关心两种方面的内存泄漏:
如何避免内存泄露
内存泄漏非常常见,解决方案分为两种:1、事前预防型。如智能指针等。2、事后查错型。如泄 漏检测工具。