C/C++ 内存管理:摆脱野指针和内存泄漏

内存问题是 C/C++ 程序员的"职业噩梦":写着写着就崩,调半天才发现是少了一行 delete;改个数组大小,realloc 用错又炸了;看到 placement new 直接跳过。其实,大部分坑都来自同一个根源------你没真正搞清"谁在什么地方,谁负责回收"。这篇文章就是一步步把这件事讲透,从内存布局到动态分配,再到 C++ 特有的内存机制,帮你把脑子里的那张"内存地图"补完整。

目录

[1. C/C++内存分布](#1. C/C++内存分布)

[2. C语言中动态内存管理方式:malloc/calloc/realloc/free](#2. C语言中动态内存管理方式:malloc/calloc/realloc/free)

[3. C++内存管理方式](#3. C++内存管理方式)

[3.1 new/delete操作内置类型](#3.1 new/delete操作内置类型)

[3.2 new和delete操作自定义类型](#3.2 new和delete操作自定义类型)

[4. new和delete操作自定义类型](#4. new和delete操作自定义类型)

[4.1 operator new与operator delete函数](#4.1 operator new与operator delete函数)

[5. new和delete的实现原理](#5. new和delete的实现原理)

[5.1 内置类型](#5.1 内置类型)

[5.2 自定义类型](#5.2 自定义类型)

[6. malloc/free和new/delete的区别](#6. malloc/free和new/delete的区别)

总结


1. C/C++内存分布

先看一段小程序,思考每个变量"住在哪一层楼"(在哪个内存区):

cpp 复制代码
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.代码段(常量区)

  • globalVar在哪里?____

  • staticGlobalVar在哪里?____

  • staticVar在哪里?____

  • localVar在哪里?____

  • num1在哪里?____

  • char2在哪里?____

  • *char2在哪里?____

  • pChar3在哪里?____

  • *pChar3在哪里?____

  • ptr1在哪里?____

  • *ptr1在哪里?____

答案是:C、C、C、A、A、A、A、A、D、A、B

几个核心概念(配合上图对照):

  • 栈(stack) :存放非静态局部变量、函数参数、部分返回现场,向下增长。函数进出由编译器自动管理,速度快。

  • 内存映射段(mmap区):高效I/O与共享库装载区域,也可用于共享内存与进程间通信(了解即可)。

  • 堆(heap) :程序运行期 动态分配的内存,向上增长,用完要手动释放。

  • 数据段(静态区) :放全局变量静态变量等。

  • 代码段:存放可执行指令与只读常量(如字符串常量)。


2. C语言中动态内存管理方式:malloc/calloc/realloc/free

cpp 复制代码
void Test()
{
    //1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof(int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    //这里需要free(p2)吗?
    free(p3);
}

是什么:

  • malloc(n) 只分配n字节,不初始化;

  • calloc(count,size) 分配count*size字节,并初始化为全0;

  • realloc(p,newSize) 按新大小调整p指向的块,可能搬家 ,返回新地址;失败则返回NULL原指针仍有效

  • free(p)释放由上述接口得到的块。

为什么:满足C中手动管理内存的需求,避免一次性固定大小。

怎么用:

  • realloc成功后旧指针可能失效,以后只用新指针 ;失败时返回NULL原指针仍可用

  • 上面示例中,p3已接管那片内存,最终只需free(p3)不再额外free(p2)

【表格:C动态分配接口对比】

接口 是否初始化 失败返回 调整大小 典型用法
malloc NULL 原始分配
calloc 是(清零) NULL 需要清零
realloc N/A NULL(原块保留) 动态扩缩容
free - - - 释放堆内存

3. C++内存管理方式

C里的接口在C++中仍可用,但对"对象的构造/析构"就不够友好,所以C++提供了new/delete

3.1 new/delete操作内置类型

  • 规则 :单对象用new/delete成片连续new[]/delete[],一定要成对匹配

3.2 new和delete操作自定义类型

cpp 复制代码
void Test()
{
    //动态申请一个int
    int* ptr4 = new int;
    //动态申请并初始化为10
    int* ptr5 = new int(10);
    //动态申请连续的int数组(示例为3个)
    int* ptr6 = new int[3];

    delete ptr4;
    delete ptr5;
    delete[] ptr6;
}

class A
{
public:
    A(int a = 0) : _a(a) { cout << "A():" << this << endl; }
    ~A() { cout << "~A():" << this << endl; }
private:
    int _a;
};

关键差异 :对自定义类型new调用构造函数delete调用析构函数 ;而malloc/free只管内存,不管构造与析构。


4. new delete 操作自定义类型

4.1 operator new与operator delete函数

  • new/delete操作符 ,它们底层会调用 全局函数operator newoperator delete来申请/释放内存。

  • 对象构造/析构并不是operator new/delete干的,是编译器在分配/释放前后插的调用流程

示例:对比自定义类型与内置类型的分配释放

cpp 复制代码
int main()
{
    //对自定义类型:new/delete除了开空间,还会调构造/析构
    A* p1 = (A*)malloc(sizeof(A));   //只分配字节,不调构造
    A* p2 = new A(1);                //分配并构造
    free(p1);
    delete p2;

    //对内置类型:行为更接近malloc/free
    int* p3 = (int*)malloc(sizeof(int));
    int* p4 = new int;               //可能做默认初始化
    free(p3);
    delete p4;

    //数组版本
    A* p5 = (A*)malloc(sizeof(A)*10);
    A* p6 = new A[10];               //为每个元素依次调用构造
    free(p5);
    delete[] p6;
    return 0;
}

底层:operator new: 该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

cpp 复制代码
void *__CRTDECL operator new(size_tsize) _THROW1(_STDbad_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_allocnomem;
            _RAISE(nomem);
        }
    }
    return p;
}

void operator delete(void* p) noexcept
{
    if(!p) return;
    free(p);
}

new做了什么?

delete做了什么?


5. new和delete的实现原理

5.1 内置类型

  • 行为与malloc/free类似;

  • 区别:new/delete单对象new[]/delete[]数组 要匹配;分配失败时,new抛异常malloc返回NULL

5.2 自定义类型

  • new原理 :①调operator new拿到原始内存;②在这块内存上执行构造函数;③返回指针。

  • delete原理 :①先执行析构函数 清理资源;②再调operator delete释放原始内存。

  • new T[N] :先申请一整块,再对每个元素依次调用N次构造

  • delete[] :对每个元素依次析构N次,最后一次性释放。


6. malloc/free和new/delete的区别

共同点 :都从 申请内存,且都需要手动释放
不同点:

维度 malloc/free new/delete
形式 函数 操作符
初始化 不做初始化 可做初始化
指定大小 传字节数 写类型,数组用[]写个数
返回值 void*需强转 直接是目标类型指针
失败语义 返回NULL需判空 bad_alloc需捕获
自定义类型 不调构造/析构 会调构造/析构

总结

  • 五大分区代码段 放++指令与只读常量++ ;数据段 放全局/静态; 用于++动态申请++ ; 用于++函数活动期++ 的数据;内存映射段 服务于++共享库和高效I/O++。

  • 成对使用new↔deletenew[]↔delete[]malloc/calloc/realloc↔free,别混用。

  • 对象语义:自定义类型一定要考虑构造/析构是否被正确调用。

  • 底层new背后是operator new(通常用malloc),失败抛异常;delete背后是operator delete(最终free)。


相关推荐
Arva .1 小时前
读写锁 (ReadWriteLock)
java·开发语言
灰灰勇闯IT1 小时前
虚拟机性能优化实战:从基础调优到深度压榨性能
开发语言·学习·性能优化·虚拟机
威哥爱编程1 小时前
【鸿蒙开发案例篇】NAPI 实现 ArkTS 与 C++ 间的复杂对象传递
c++·harmonyos·arkts
霸王大陆1 小时前
《零基础学PHP:从入门到实战》教程-模块八:面向对象编程(OOP)入门-5
开发语言·笔记·php·课程设计
0 0 01 小时前
CCF-CSP 37-3 模板展开(templating)【C++】
c++·算法
毕设源码-郭学长1 小时前
【开题答辩全过程】以 基于java的校园安全管理系统为例,包含答辩的问题和答案
java·开发语言
ranchor6661 小时前
pandas 模拟题
开发语言·python·pandas
埃伊蟹黄面1 小时前
二分查找算法
c++·算法·leetcode
xun_xin6662 小时前
如何解决Qt与OpenCV编译器不匹配问题
开发语言·qt·opencv