服务器其它

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > 服务器其它 > C++ 内存管理

C++ 内存管理深入解析

作者:xclic

C++内存管理分栈、堆、全局/静态区等,需手动控制动态内存分配,通过new/delete管理对象生命周期,推荐使用智能指针和RAII原则避免内存泄漏、悬空指针等错误,确保程序安全高效运行,本文给大家介绍c++内存管理的相关知识,感兴趣的朋友一起看看吧

C++ 内存管理是程序设计的核心环节,直接影响程序的性能、稳定性和安全性。C++ 不像 Java、Python 等语言有自动垃圾回收机制,而是需要开发者手动管理动态内存(或通过智能指针等机制自动管理)。

1、C++ 内存分区

内存区域存储内容生命周期管理方式
栈 (Stack)函数参数、局部变量、函数返回值等自动管理。在作用域开始时分配,作用域结束时自动释放。编译器自动生成代码管理,效率极高。
堆/自由存储区 (Heap/Free Store)动态分配的内存手动管理。从 new 开始到 delete 结束。程序员显式控制。分配和释放速度较慢,容易出错。
全局/静态存储区 (Global/Static)全局变量、静态变量(static)、字面量整个程序运行时。在 main 开始前初始化,main 结束后销毁。编译器管理。
常量区 (Constant)字符串字面量和其他常量整个程序运行时。编译器管理。通常不可修改。
代码区 (Code/Text)程序的二进制代码(函数体)整个程序运行时。编译器管理。

图示:

+-----------------------+
|       栈 (Stack)      | <- 高地址,向下增长
+-----------------------+
|          ↓            |
|                       |
|          ↑            |
+-----------------------+
|       堆 (Heap)       | <- 低地址,向上增长
+-----------------------+
| 全局/静态区 (Global)   |
+-----------------------+
|   常量区 (Constants)   |
+-----------------------+
|   代码区 (Code/Text)   |
+-----------------------+ 

2、栈

void stackExample() {
    int x = 10; // `x` 在栈上分配
    std::string name = "Alice"; // `name` 对象本身在栈上,但其内部的动态数据可能在堆上
    double data[100]; // 数组 `data` 在栈上分配(如果100很大,可能导致栈溢出)
} // 作用域结束,`x`, `name`, `data` 被自动销毁。
   // `std::string` 的析构函数会被调用,释放它可能占用的堆内存。

注意:不要返回指向栈内存的指针或引用!

int* dangerousFunction() {
    int localVar = 42;
    return &localVar; // 严重错误!返回后 localVar 已被销毁,指针悬空。
}

3、堆

3.1 动态分配与释放:new / delete

new 运算符完成两件事:1) 在堆上分配足够的内存;2) 在该内存上构造对象(调用构造函数)。
delete 运算符也完成两件事:1) 调用对象的析构函数;2) 释放该对象占用的内存。

// 动态分配一个 int,并初始化为 5
int* ptr = new int(5); 
// 动态分配一个 MyClass 对象,调用其构造函数
MyClass* objPtr = new MyClass("Name", 10); 
// ... 使用 ptr 和 objPtr ...
// 释放内存
delete ptr;    // 释放 int
delete objPtr; // 调用 ~MyClass(),然后释放内存
ptr = nullptr; // 良好实践:释放后立即置空,防止悬空指针
objPtr = nullptr;

3.2 分配/释放对象数组

// 动态分配一个包含10个int的数组
int* arrayPtr = new int[10]; 
// 动态分配3个MyClass对象,调用它们的默认构造函数
MyClass* objArrayPtr = new MyClass[3]; 
// ... 使用数组 ...
// 释放数组内存。必须使用 delete[]!
delete[] arrayPtr;     // 正确:释放数组
delete[] objArrayPtr;  // 正确:调用每个元素的析构函数,然后释放内存
// delete objArrayPtr; // 灾难性错误!行为未定义。只会调用第一个元素的析构函数,然后错误地释放内存。

3.3 new/delete和malloc/free

C++ 提供两种动态内存管理方式:C 语言兼容的 malloc/free,以及 C++ 特有的 new/delete

重要规则: 绝对不要混用! 用 new 分配的内存必须用 delete 释放;用 malloc() 分配的内存必须用 free() 释放。

3.3.1 malloc/free(C 风格)

函数原型

void* malloc(size_t size);  // 分配 size 字节的内存,返回 void*(需强转)
void free(void* ptr);       // 释放 ptr 指向的内存(ptr 必须是 malloc 分配的地址)

特点

示例

int* p = (int*)malloc(sizeof(int)); // 分配 int 大小的内存(未初始化)
*p = 10;                            // 手动赋值
free(p);                            // 释放内存(p 变为野指针,建议置空)
p = nullptr;

3.3.2 new/delete(C++ 风格)

new/delete 是 C++ 对动态内存管理的增强,不仅分配/释放内存,还会自动调用对象的构造函数析构函数,是管理类对象的首选方式。

基本用法

// 1. 分配单个对象
MyClass* obj = new MyClass(10);  // 调用 MyClass(int) 构造函数
delete obj;                      // 调用 MyClass 析构函数,释放内存

// 2. 分配数组(必须用 new[] 和 delete[] 匹配)
MyClass* arr = new MyClass[5];   // 调用 5 次 MyClass 默认构造函数
delete[] arr;                    // 调用 5 次 MyClass 析构函数,释放数组

new 的底层原理:
new 操作分两步:

调用 operator new(size_t) 分配内存(类似 malloc);

在分配的内存上调用对象的构造函数。

delete 的底层原理:
delete 操作分两步:

调用对象的析构函数;

调用 operator delete(void*) 释放内存(类似 free)。

3.4 常见动态内存错误

3.4.1 内存泄漏 (Memory Leak)

分配了内存但忘记释放。
cpp void leak() { int* ptr = new int(100); // ... 使用了 ptr ... return; // 忘记 delete ptr; 内存泄漏! }

3.4.2 悬空指针 (Dangling Pointer)

指针指向的内存已被释放。
cpp int* ptr = new int(50); delete ptr; // 内存被释放 *ptr = 10; // 错误!ptr 现在是悬空指针,解引用它是未定义行为。

3.4.3 双重释放 (Double Free)

对同一块内存释放两次。
cpp int* ptr = new int(50); delete ptr; delete ptr; // 灾难!未定义行为,通常导致程序崩溃。

3.4.4 野指针 (Wild Pointer)

未初始化的指针。
cpp int* ptr; // 野指针,指向随机地址 *ptr = 10; // 极度危险!未定义行为。

4、全局/静态区

特点

示例

int g_var = 10;         // 全局变量,存储在全局区
static int s_var = 20;  // 静态全局变量,存储在全局区
void func() {
    static int s_local = 30; // 静态局部变量,存储在全局区(仅初始化一次)
}

5、常量区

const int c_var = 100;   // const 常量,存储在常量区
char* str = "hello";     // "hello" 存储在常量区,str 是栈上的指针

6、常见问题

std::weak_ptr<MyClass> weak = ...;
if (auto shared = weak.lock()) { // 检查返回的shared_ptr是否为空
    // 对象还存在,可以安全使用 shared
    shared->doSomething();
} else {
    // 对象已被释放
    std::cout << "Object is gone.\n";
}

悬空指针:指针指向的内存已被释放,但指针本身未被置空。解引用它是未定义行为

int* ptr = new int(10);
delete ptr; // 内存释放
// ptr 现在是悬空指针
*ptr = 20; // 未定义行为!
ptr = nullptr; // 良好实践:释放后立即置空。

野指针:未被初始化的指针,其值是随机的垃圾地址。

int* ptr; // 野指针
*ptr = 10; // 极度危险!未定义行为。
int* ptr2 = nullptr; // 正确:总是初始化指针。

delete:用于释放 new 分配的单个对象。它会调用该对象的析构函数。

delete[]:用于释放 new[] 分配的对象数组。它会调用数组中每个元素的析构函数,然后释放整块内存。

混用的后果:未定义行为。最常见的后果是程序崩溃。

用 delete 释放数组:只会调用第一个元素的析构函数,然后错误地释放内存。

用 delete[] 释放单个对象:会试图析构多个不存在的对象,导致内存结构被破坏。

到此这篇关于C++ 内存管理的文章就介绍到这了,更多相关C++ 内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文