深入理解C/C++的内存管理

在C和C++中,高效的内存管理是编写性能优化和资源高效利用程序的关键。本文将深入探讨C/C++内存管理的各个方面,包括内存的分布、C语言和C++中的动态内存管理方式,以及newdelete操作符的使用

C/C++内存分布

C和C++程序的内存可以分为以下几个区域:

  • 栈(Stack):自动存储局部变量。当函数被调用时,局部变量在栈上创建,函数返回时,这些变量被销毁。
  • 堆(Heap):用于动态内存分配。与栈不同,堆上的内存分配和释放需要手动管理。
  • 全局/静态存储区:存放全局变量和静态变量。这部分内存在程序启动时分配,在程序结束时释放。
  • 文字常量区:常量字符串等常量数据存放的地方。它们在程序生命周期内不变。
  • 程序代码区:存放程序执行代码的内存区域。

理解这些区域对于避免内存泄漏和优化程序性能至关重要。

C语言中动态内存管理方式

C语言提供了几种动态内存管理的方法,包括:

  • malloc:分配指定大小的内存块。分配的内存未初始化,可能包含垃圾数据。
  • calloc:分配并初始化指定数量的内存块。初始化为零。
  • realloc:重新分配之前分配的内存块的大小。
  • free:释放之前分配的内存块。

这些函数允许程序在运行时根据需要分配和释放内存,但也需要开发者负责管理这些内存,以避免内存泄漏。

cpp 复制代码
#include <stdlib.h>

void dynamicMemoryExample() {
    int* ptr = (int*)malloc(sizeof(int) * 4); // 分配内存
    if (ptr != NULL) {
        for (int i = 0; i < 4; i++) {
            ptr[i] = i; // 使用内存
        }
    }
    free(ptr); // 释放内存
}

C++中动态内存管理

C++提高了动态内存管理的抽象层次,通过newdelete操作符提供更为直观和安全的方式来处理内存。

  • 使用new分配内存不仅会分配内存,还会调用对象的构造函数,这提供了初始化对象的机会。
  • 使用delete释放内存时,会调用对象的析构函数,然后释放内存,这提供了清理资源的机会
cpp 复制代码
#include <iostream>

void dynamicMemoryExampleInCpp() {
    int* ptr = new int(10); // 动态分配内存并初始化
    std::cout << *ptr << std::endl; // 使用内存
    delete ptr; // 释放内存,并调用析构函数
}
cpp 复制代码
void Test()
{
  // 动态申请一个int类型的空间
  int* ptr4 = new int;
  
  // 动态申请一个int类型的空间并初始化为10
  int* ptr5 = new int(10);
  
  // 动态申请10个int类型的空间
  int* ptr6 = new int[3];
  delete ptr4;
  delete ptr5;
  delete[] ptr6;
}

注意:申请和释放单个元素的空间,使用 new delete 操作符,申请和释放连续的空间,使用
new\[\] delete\[\] ,注意:匹配起来使用。

newdelete****操作自定义类型

cpp 复制代码
class A
{
public:
 A(int a = 0)
 : _a(a)
 {
 cout << "A():" << this << endl;}
 ~A()
 {
 cout << "~A():" << this << endl;
 }
private:
 int _a;
};
int main()
{
 // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
还会调用构造函数和析构函数
 A* p1 = (A*)malloc(sizeof(A));
 A* p2 = new A(1);
 free(p1);
 delete p2;
 // 内置类型是几乎是一样的
 int* p3 = (int*)malloc(sizeof(int)); // C
 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;
}

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与
free不会。

operator new与operator delete函数

在C++中,newdelete不仅仅是操作符,它们背后实际上是调用了operator newoperator delete函数。这两个函数负责分配和释放动态内存。重要的是,C++允许开发者重载这些函数来提供自定义的内存管理策略。

自定义内存管理

自定义内存管理通常用于优化性能,例如通过实现特定的内存池来避免频繁地向操作系统请求小块内存。这样可以显著减少内存碎片以及提高内存分配和释放的效率。

cpp 复制代码
#include <iostream>
#include <cstdlib>

void* operator new(size_t size) {
    std::cout << "Custom new for size: " << size << std::endl;
    return malloc(size);
}

void operator delete(void* memory) noexcept {
    std::cout << "Custom delete" << std::endl;
    free(memory);
}

通过这个简单的例子,我们看到了如何拦截所有的newdelete调用,以便在分配和释放内存时执行自定义逻辑

new和delete的实现原理

new操作符在底层调用operator new函数来分配内存,并在成功分配内存后调用对象的构造函数。相似地,delete操作符首先调用对象的析构函数,然后调用operator delete函数来释放内存。

这种分离使得newdelete不仅仅关注内存的分配和释放,还能确保对象生命周期的正确管理。
如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似,不同的地方是:
new/delete 申请和释放的是单个元素的空间, new\[\] 和 delete\[\] 申请的是连续空间,而且 new 在申
请空间失败时会抛异常, malloc 会返回 NULL

自定义类型
new 的原理

  1. 调用 operator new 函数申请空间
  1. 在申请的空间上执行构造函数,完成对象的构造
    delete 的原理
  1. 在空间上执行析构函数,完成对象中资源的清理工作
  1. 调用 operator delete 函数释放对象的空间
    new TN 的原理
  1. 调用 operator new\[\] 函数,在 operator new\[\] 中实际调用 operator new 函数完成 N 个对
    象空间的申请
  1. 在申请的空间上执行 N 次构造函数
    delete\[\] 的原理
  1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理
  2. 调用 operator delete\[\] 释放空间,实际在 operator delete\[\] 中调用 operator delete 来释
    放空间

也就是说当调用delete\[\]的时候,地址的第一个字节处存放的是n,也就是需要调用n次析构,如果我们使用的是delete,默认从当前位置开始析构,那么位置就会出错。

定位new表达式(placement-new)

定位new表达式是C++中的一个高级特性,允许在已分配的内存上构造对象。这对于内存池、缓存管理等场景非常有用,因为它允许重复使用已分配的内存来创建新对象,避免了频繁的内存分配和释放操作。

cpp 复制代码
#include <new> // 必须包含这个头文件

char buffer[1024]; // 预分配内存

void placementNewExample() {
    int* p = new (buffer) int(10); // 在buffer上构造int对象
    // 使用p...
}

使用定位new表达式时,需要确保操作的内存足够大且正确对齐,以容纳指定类型的对象。

常见面试题

内存区域划分

问:请描述C/C++程序中的内存区域划分。

答:C/C++程序的内存分为栈、堆、全局/静态存储区、文字常量区和程序代码区。

动态内存管理

问:mallocnew的区别是什么?

答:malloc仅分配内存,不调用构造函数;new分配内存的同时调用构造函数初始化对象。相应地,freedelete的区别在于delete会先调用析构函数再释放内存。

定位new

问:什么是定位new表达式,它有什么用途?

答:定位new表达式允许在已分配的内存上构造对象。这在需要在特定位置创建对象,或者在内存池、缓存管理等场景下重复使用内存时非常有用。


通过本文,我们深入探讨了C/C++中的内存管理,从基本的内存区域划分到高级特性如定位new表达式,以及如何通过重载operator newoperator delete来实现自定义内存管理策略。理解这些概念不仅对于写出高性能的C/C++代码至关重要,也是面试中常见的问题。希望这篇博客能帮助你在C/C++内存管理方面达到新的高度!

相关推荐
郝学胜_神的一滴1 天前
CMake 034:生成器表达式:解耦构建时序、精简分支逻辑的终极利器
c++·cmake
见过夏天2 天前
C++ 基础入门完全指南
c++
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
BadBadBad__AK3 天前
线段树维护区间 k 次方和
c++·数学·算法·stl
卷无止境4 天前
Eigen 库如何借助 OpenMP 加速计算
c++·后端
卷无止境4 天前
OpenMPI、MPICH 与 OpenMP:关系、核心概念与架构全解
c++·后端
郝学胜_神的一滴5 天前
CMake 30:循环语法全解|foreach_while双循环精讲、迭代技巧与实战避坑指南
c++·cmake
卷无止境7 天前
C++ 的Eigen 库全解析
c++
卷无止境7 天前
现代 C++特性大盘点:一门脱胎换骨的老语言
c++·后端
郝学胜_神的一滴7 天前
CMake 27:缓存变量的特性、语法、类型与实操全解
c++·cmake