【内存分配】02.C++内存管理

【嵌入式八股】一、语言篇(本专栏)https://www.nowcoder.com/creation/manager/columnDetail/mwQPeM

【嵌入式八股】二、计算机基础篇https://www.nowcoder.com/creation/manager/columnDetail/Mg5Lym

【嵌入式八股】三、硬件篇https://www.nowcoder.com/creation/manager/columnDetail/MRVDlM

【嵌入式八股】四、嵌入式Linux篇https://www.nowcoder.com/creation/manager/columnDetail/MQ2bb0

C++内存管理

33.C++的内存管理

和C基本一致

代码区(.text):也称为文本区,存放程序的可执行代码。

常量区(.rodata):也称只读数据段,只读代码段

全局区(Global/Static Segment):存放全局变量、静态变量和常量。程序在编译后,分配这些数据的空间。

栈区(Stack Segment):存放函数调用时的参数、返回地址、局部变量等。栈是一种先进后出的数据结构,可以用来保存函数调用的现场。

堆区(Heap Segment):动态分配的内存空间,例如使用 new 或 malloc 等函数时分配的内存。堆的大小可以动态增长或缩小,程序员需要手动管理其生命周期。

与C语言不同之处在于,C++中还有类的成员变量和虚函数表等数据结构,这些数据结构的内存分配和管理方式有所不同,例如,类的成员变量通常在对象的堆或栈上分配内存,虚函数表存储在只读数据段(.rodata)、虚函数存储在代码段(.text)、虚表指针的存储的位置与对象存储的位置相同,可能在栈、也可能在堆或数据段等

你可能在其他地方还会看到以下概念:

映射区,不是C++中的内存分区,它是在Unix/Linux操作系统中的一种内存映射区域,用于将磁盘上的文件映射到内存中,从而实现文件的快速读取和写入。

自由存储区,是C++中的术语,通常与堆区是同义词。在C++中,使用 newmalloc 等函数分配的内存被称为自由存储区,它与堆区的概念是一致的,是一种动态分配的内存空间,程序员可以在其中分配和释放内存。

34.C++如何处理返回值?

在C++中,函数的返回值可以是任何类型,包括基本类型、自定义类型和指针等。

函数的返回值通常通过 return 语句来指定,具体方式如下:

  1. 基本类型的返回值:直接将值作为 return 语句的返回值即可。
  2. 自定义类型的返回值:返回值需要通过值传递或引用传递的方式返回。如果采用值传递方式,则返回值会被复制到函数调用点的栈上,这种方式适用于返回较小的对象。如果采用引用传递方式,则返回值直接指向已经分配的内存,这种方式适用于返回较大的对象。
class Person {
public:
    string name;
    int age;
};

// 通过值传递方式返回自定义类型
Person createPerson(string name, int age) {
    Person p;
    p.name = name;
    p.age = age;
    return p;
}

// 通过引用传递方式返回自定义类型
void createPerson(string name, int age, Person& p) {
    p.name = name;
    p.age = age;
}
  1. 指针类型的返回值:可以直接返回指针或通过引用返回指针
int* createArray(int n) {
    int* arr = new int[n];
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

void createArray(int n, int*& arr) {
    arr = new int[n];
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
}

需要注意的是,返回值是在函数调用栈上分配的临时空间,它在函数返回后就会被释放。如果将一个指向临时空间的指针返回给调用者,那么调用者在访问这个指针时就会引发未定义行为,因此,返回指向临时空间的指针是一种常见的错误做法,需要避免。

#include <iostream>

using namespace std;

int* createArray(int n) {
    int arr[n];               //<----------------------------------------
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

int main() {
    int* p = createArray(5);
    for (int i = 0; i < 5; i++) {
        cout << p[i] << " ";
    }
    return 0;
}

在这个示例代码中,函数 createArray 返回一个指向数组的指针,而数组 arr 是在函数内部定义的临时变量,它所占用的内存是在函数栈帧中分配的,当函数执行完毕后,栈帧被销毁,arr 所占用的内存也被释放。

main 函数中,通过调用 createArray 函数,获取了一个指向 arr 的指针 p。在使用 p 访问数组元素时,由于 arr 所占用的内存已经被释放,所以 p 实际上成为了一个悬空指针,访问它指向的内存区域会导致未定义行为,例如程序崩溃等。

为了避免这种问题,应该尽量避免返回指向临时空间的指针,而是返回指向动态分配内存的指针或者采用传参的方式,将需要返回的对象作为参数传入。

正确用法如下:

#include <iostream>

using namespace std;

int* createArray(int n) {
    int* arr = new int[n];     //<-----------------------------------------
    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
    }
    return arr;
}

int main() {
    int* p = createArray(5);
    for (int i = 0; i < 5; i++) {
        cout << p[i] << " ";
    }
    delete[] p; // 记得释放内存
    return 0;
}
35.malloc new的区别

mallocnew都可以用于在堆上分配内存空间,但它们的行为和用法是有所不同的。

分配空间的大小

malloc函数的参数是所需空间的字节数,而new关键字的参数是要分配空间的数据类型。在使用new关键字时,编译器会自动计算所需空间的大小,并分配足够的内存空间。

返回值

malloc函数返回的是分配内存空间的起始地址,通常需要将该地址进行强制类型转换,才能使用它。

new关键字返回的是指向分配的空间的指针,不需要进行类型转换,可以直接使用。

内存分配失败的处理

malloc函数在分配内存空间失败时,会返回一个空指针NULL,需要程序员手动检查返回值来判断是否分配成功

new关键字在分配内存空间失败时,会抛出一个std::bad_alloc异常,程序员需要通过try...catch语句来捕获该异常。

内存空间的初始化

malloc函数分配的内存空间并不会进行初始化,它返回的是一段未初始化的内存区域

new关键字分配的内存空间会进行初始化,对于内置类型,会进行默认初始化,而对于自定义类型,会调用构造函数进行初始化。

内存空间的释放

malloc函数分配的内存空间需要使用free函数进行手动释放。

new关键字分配的内存空间需要使用delete运算符进行手动释放,而对于数组类型,需要使用delete[]运算符进行释放。

记忆:大回失始释

还有很多不同点,不详细总结了,能说几个不错了

特征 new/delete malloc/free
分配内存的位置 自由存储区
内存分配失败返回值 完整类型指针 void*
内存分配失败返回值 默认抛出异常 返回NULL
分配内存的大小 由编译器根据类型计算得出 必须显式指定字节数
处理数组 有处理数组的new版本new[] 需要用户计算数组的大小后进行内存分配
已分配内存的扩充 无法直观地处理 使用realloc简单完成
是否相互调用 可以,看具体的operator new/delete实现 不可调用new
分配内存时内存不足 客户能够指定处理函数或重新制定分配器 无法通过用户代码进行处理
函数重载 允许 不允许
构造函数与析构函数 调用 不调用

放个示例

// 使用malloc分配内存空间
int* p1 = (int*)malloc(sizeof(int));
*p1 = 10;
free(p1); // 释放内存空间

// 使用new关键字分配内存空间
int* p2 = new int(20);
delete p2; // 释放内存空间

// 使用new关键字分配数组内存空间
int* p3 = new int[3] { 30, 40, 50 };
delete[] p3; // 释放内存空间

// 使用new关键字分配自定义类型的内存空间
Person* p4 = new Person();
delete p4;
36.delete p、delete [] p

delete p用于释放使用new运算符分配的单个对象的内存空间。

int* p = new int(10);
delete p;

delete [] p用于释放使用new[]运算符分配的数组对象的内存空间。

需要注意的是,在使用new[]运算符分配数组对象的内存空间时,必须使用delete[]运算符进行释放,否则会导致未定义的行为。

int* p = new int[10];
delete[] p;
37.allocator有什么作用,和new区别?

allocator 解决了new的局限性_std::allocator和new_我什么都布吉岛的博客-CSDN博客

无论是单个对象还是内容是对象的数组,其空间分配和初始化是绑定在一起执行的,你需要多少对象,我就分配多少空间,然而有些时候我们并不知道我们需要多少对象。

假设我们想要动态创建一个不多于100个的string对象数组,因为实现不知道需要多少对象,所以我们使用了下面的语句创建这个数组:

string * new string[100];
//下面可能对其中的string对象进行赋值

实际使用可能只用到了一个,也可能用到了100个,但是为了保证任务要求我们不得不创建了100个string空间并进行了构造。

std::allocator定义在头文件#include<memory>中,和new不同,他将空间分配和对象构造分离,是一种内存感知内存分配方法,它能够根据对象类型来选择恰当的内存对齐位置和大小。其内存的特点是:分配的内存是原始的、未构造的

allocator提供了allocatedeallocate两个成员函数,分别用于分配和释放内存空间。其中,allocate函数接受一个整数参数,表示需要分配的元素个数,返回指向第一个元素的指针。deallocate函数接受两个参数,第一个参数是指向分配的内存空间的指针,第二个参数是需要释放的元素个数。

需要注意的是,allocator只负责分配和释放内存空间,并不负责对象的构造和析构。因此,在使用allocator分配内存空间时,需要手动调用对象的构造函数进行初始化,以及手动调用析构函数进行清理。

#include <iostream>
#include <memory>

using namespace std;

int main() {
    allocator<int> alloc; // 创建int类型的allocator对象
    int* p = alloc.allocate(10); // 分配10个int类型的内存空间
    for (int i = 0; i < 10; i++) {
        p[i] = i * 2;
        cout << p[i] << " ";
    }
    alloc.deallocate(p, 10); // 释放内存空间

    return 0;
}
38.malloc与free的实现原理?

malloc 背后的虚拟内存 和 malloc实现原理 - 知乎 (zhihu.com)

mallocfree是C语言中用于动态内存分配和释放的函数。它们的底层实现依赖于操作系统提供的系统调用,例如brkmmap

当调用malloc函数时,它会向操作系统请求一块连续的内存空间,该空间的大小由用户传递给malloc函数的参数决定。操作系统会寻找一块足够大的空闲内存区域,并将其标记为已分配状态,然后将该内存区域的起始地址返回给调用者。

当调用free函数时,它会将之前通过malloc函数分配的内存空间释放回操作系统。free函数并不会直接将内存空间返回给操作系统,而是将其标记为未分配状态,以便后续的ma函数可以再次使用该内存空间。在某些情况下,操作系统会将标记为未分配状态的内存空间合并成更大的空闲内存区域,以便后续的内存分配请求可以得到更大的内存空间。

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

【嵌入式八股】一、语言篇 文章被收录于专栏

查阅整理上千份嵌入式面经,将相关资料汇集于此,主要包括: 0.简历面试 1.语言篇【本专栏】 2.计算机基础 3.硬件篇 4.嵌入式Linux (建议PC端查看)

全部评论

相关推荐

有工作后先养猫:太好了,是超时空战警,我们有救了😋
点赞 评论 收藏
分享
评论
2
1
分享
牛客网
牛客企业服务