【C/C++】动态内存管理(C:malloc,realloc,calloc,free)

简介: 探索C++与C语言的动态内存管理:从malloc到new/delete,了解内存分布及栈、堆的区别。文章涵盖malloc、realloc、calloc与free在C中的使用,强调内存泄漏的风险。C++引入new和delete,支持对象构造与析构,还包括operator new和placement-new。深入分析内存管理机制,揭示C与C++在内存处理上的异同。别忘了,正确释放内存至关重要!

🔥个人主页: Forcible Bug Maker
🔥专栏: C++ | | C语言

目录
前言
C/C++内存分布
C语言中的动态内存管理:malloc/realloc/realloc/free
malloc
realloc
calloc
free
C++中的动态内存管理:new/delete
new和delete操作内置类型
new和delete操作自定义类型
operator new与operator delete函数
new和delete的实现原理
定位new表达式(placement-new)
结语
前言
本篇博客主要内容:C++和C语言的动态内存管理方式,机制以及两者之间的区别。

在学习C语言的过程中,也曾涉及过动态内存管理,我们可以使用malloc,realloc,calloc等函数来动态管理堆中空间资源。而在C++中,有了新的动态内存管理方式,那就是new和delete关键字。忽然发现之前似乎并没有讲C语言的几个动态内存管理函数,所以标题是 【C/C++】动态内存管理 ,不过不止如此,本次还会介绍new,delete关键字的底层,并区分一下C和C++内存管理之间的不同。

C/C++内存分布
在开始讲解之前,想通过一道题引入今天的内容。

include

using namespace std;
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);
}

数据段就是我们常说的静态区,代码段就是我们所说的常量区。而动态内存管理,主要管理的是堆区中的内存空间。

选择题:
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)
①g/lobalVar在哪里?②_ staticGlobalVar在哪里?
③//staticVar在哪里?_ ④localVar在哪里?__
⑤num1 在哪里?__

globalVal是全局变量,存储在数据段(静态区)。
staticGlobalVal是静态全局变量,同时是静态的,因此它同样存储在数据段(静态区)。
staticVar是静态局部变量,存储在数据段(静态区),其生命周期贯穿整个程序的执行。
localVar是局部变量,存储在栈上。
num1是局部变量,本质是数组指针(存储着数组首元素地址)存储在栈上。
选择题:
①char2在哪里? ②char2在哪里?
③pChar3在哪里? ④pChar3在哪里?___
⑤ptr1在哪里? ⑥*ptr1在哪里?

char2和num1性质是一样的,为局部变量,是数组首元素地址,存储在栈上。
char2是栈帧中直接开辟空间存储的数据,在栈上。
pChar3是局部指针变量,存储在栈上。
pChar3指向的内容(为常量字符串"abcd")存储在代码段(常量区)。
ptr1是局部指针变量,存储在栈上。
ptr1指向的内存空间是通过malloc动态开辟的,存储在堆上。 char2(局部字符数组)
当声明一个局部字符数组并用一个字符串字面量初始化的时候,如char char2[] = "abcd";或char char2[5] = "abcd";时,编译器在栈上会为数组分配内存,然后将字符串字面量的内容(包括结尾的\0)复制到这块内存中。因此char2指向的内容存储在栈上。
pChar(字符串字面量指针)
当你使用指针去指向一个字符串常量(“abcd”)时,由于字符串常量是存储在代码段(常量区) 的,所以必须用const修饰才能接收到常量内容的地址(这样规定的原因是为了防止你对常/量做出修改,此时的"abcd"就跟数字1,2,3等等常量的性质是一样的)。故const char
pChar3 = "abcd";中,尽管pChar是一个指针,存储在栈中,但其却指向的字符串却存储在常量区。
char2不在常量区,因为char2是局部字符数组,其内容直接存储在栈上。 pChar3在常量区,是因为它指向的是一个字符串字面量(“abcd”),字符串字面量存储在程序的常量区,这部分内存只能读,不能改动。

内存主要可以分为几个部分:栈(Stack)、堆(Heap)、数据段(Data Segment)和代码段(Code Segment)。而我们的数据通常也存储在这些地方。

栈(Stack):
自动变量(包括局部变量和函数参数)通常分配在栈上。当函数被调用时,它的参数和局部变量会在栈上分配空间。当函数返回时,这些空间会被自动释放。
栈内存分配和释放由编译器自动处理,不需要程序员手动管理。
堆(Heap):
通过malloc、calloc、realloc等函数分配的内存位于堆上。堆用于动态内存分配,程序员需要手动管理内存的分配和释放。
堆上的内存可以在程序的运行期间随时分配和释放,适用于需要动态创建和销毁的对象。
静态存储区:
全局变量和静态变量(无论是在函数内部还是外部声明的静态变量)存储在全局/静态存储区。
这些变量在程序的整个生命周期中都存在,不会被自动释放。它们的初始化发生在程序启动时,释放则发生在程序结束时。
常量区:
存储常量字符串和字面量的地方。这些常量在程序的生命周期内都是固定的,不会被修改。
C语言中的动态内存管理:malloc/realloc/realloc/free
C语言里,关于动态内存的管理,是依靠一套标准的库函数完成的,它们包括malloc,realloc,calloc和free。这些函数允许在程序中随时开辟,分配和调整堆中的内存,都统一放在头文件中。下面是关于这些函数的基本用法以及它们之间的区别:

malloc
头文件:
函数声明: void malloc(size_t size);
功能: 在堆中开辟指定字节数的未初始化内存,并返回一个指向新开辟内存的指针。如果分配失败,则返回空指针NULL。
示例:
// 为代码开辟了大小能存放四个整型数据的空间
int
ptr = (int)malloc(sizeof(int) 4);
// 这个ptr指针,指向内存空间首元素地址
ptr = 1; (ptr + 1) = 2;
(ptr + 2) = 3; (ptr + 3) = 4;
for (int i = 0; i < 4; i++)
printf("%d ", *(ptr + i));
printf("\n");

realloc
头文件:
函数声明: void realloc(void ptr,size_t size);
功能: 调整之前调用malloc或calloc分配的内存块(传入的ptr为针指向此内存块的指针,如果ptr为NULL,那么realloc的功能就等同于malloc)。如果新的大小大于原始大小,可能会移动内存块到新的位置以提供足够的空间(移动过程也会把内存块中的值相应的拷贝到新的空间)。
示例:
// 为代码开辟了大小能存放四个整型数据的空间
int ptr = (int)malloc(sizeof(int) 4);
// 这个ptr指针,指向内存空间首元素地址
ptr = 1;
(ptr + 1) = 2; (ptr + 2) = 3;
(ptr + 3) = 4;
//此时空间已经被放满
ptr = (int
)realloc(ptr, sizeof(int) 5); (ptr + 4) = 5;
for (int i = 0; i < 5; i++)
printf("%d ", *(ptr + i));
printf("\n");

在上面的示例代码中,使用realloc的方式是极其不安全且不被推荐的,当realloc开辟空间失败时,返回值NULL会让原本ptr指向的空间也丢失掉,所以使用realloc时,应该像下面这样。

int ptr = (int)malloc(sizeof(int) 4);
int
tmp = (int)realloc(ptr, sizeof(int) 10);
// 判断是否realloc成功
if (tmp == NULL) {
perror("realloc fail:");
exit(1);
}
// 此时将tmp赋值给ptr就是安全的
ptr = tmp;

calloc
头文件:
函数声明: void calloc(size_t num,size_t size);
功能: 为指定数量的元素分配内存,每个元素的大小在第二个参数中指定,并自动初始化所有位为0。如果分配失败,返回NULL。
示例:
// 为代码开辟了大小能存放四个整型数据的空间
// 同时初始化所有位为0
int
ptr = (int)calloc(4, sizeof(int));
for (int i = 0; i < 4; i++)
printf("%d ",
(ptr + i));
printf("\n");
// 这个ptr指针,指向内存空间首元素地址,
ptr = 1; (ptr + 1) = 2;
(ptr + 2) = 3; (ptr + 3) = 4;
for (int i = 0; i < 4; i++)
printf("%d ", *(ptr + i));
printf("\n");

free
头文件:
函数声明: void free(void ptr);
功能: 释放之前通过malloc,calloc或realloc分配的内存。一但内存被释放,被释放那块内存就不能再被访问了。
示例:
int ptr1 = (int)malloc(sizeof(int) * 4);

int ptr2 = (int)malloc(sizeof(int) 4);
int
ptr2 = (int)realloc(ptr2, sizeof(int) 10);

int ptr3 = (int)calloc(4, sizeof(int));

free(ptr1);
free(ptr2);
free(ptr3);

注:
在你开辟使用完堆中内存但忘记free释放时,编译器不会报错,但是被开辟的这块空间会一直存在于堆中,无法再次被开辟使用,导致内存泄露。内存泄露在大型项目中是个严重的暗病,不会立即显示,但是非常可能会在项目运行时冷不丁让空间开辟失败导致程序崩溃。
同时,不要尝试去释放未经分配的内存块或者多次释放同一个内存块,可能会导致未定义的行为,因此,每个分配的内存块至多free一次。

相关文章
|
15天前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
|
4月前
|
存储 程序员 编译器
玩转C++内存管理:从新手到高手的必备指南
C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
|
3月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
6月前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
283 68
|
4月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
238 0
|
5月前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
150 0
|
7月前
|
存储 缓存 C语言
【c++】动态内存管理
本文介绍了C++中动态内存管理的新方式——`new`和`delete`操作符,详细探讨了它们的使用方法及与C语言中`malloc`/`free`的区别。文章首先回顾了C语言中的动态内存管理,接着通过代码实例展示了`new`和`delete`的基本用法,包括对内置类型和自定义类型的动态内存分配与释放。此外,文章还深入解析了`operator new`和`operator delete`的底层实现,以及定位new表达式的应用,最后总结了`malloc`/`free`与`new`/`delete`的主要差异。
134 3
|
7月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
470 4
|
8月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
414 22
|
8月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。