C++内存管理

157 阅读8分钟

3. 内存管理

3.1 malloc, realloc, calloc

malloccallocrealloc 是在C语言中用于动态内存分配的三个相关函数,它们有一些区别:

  1. malloc:

    • 功能: 分配指定大小的内存块。
    • 用法: void* malloc(size_t size);
    • 注意: 返回的内存块中的内容是未初始化的,可能包含垃圾值。
  2. calloc:

    • 功能: 分配指定数量和大小的连续内存块,并将内存块中的所有位初始化为零。
    • 用法: void* calloc(size_t num, size_t size);
    • 注意: 返回的内存块中的所有位都被初始化为零。
  3. realloc:

    • 功能: 重新调整之前分配的内存块的大小。

    • 用法: void* realloc(void* ptr, size_t size);

    • 注意

      :

      • 如果成功,返回指向重新调整大小后的内存块的指针。
      • 如果失败,返回NULL,并且原内存块保持不变。
      • 可以用于扩大或缩小内存块。
      • 可能会移动已分配的内存块到一个新的位置。

以下是使用 malloccallocrealloc 的简单示例:

  1. malloc 示例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr;
        int size = 5;
    
        // 使用 malloc 分配内存
        arr = (int*)malloc(size * sizeof(int));
    
        if (arr == NULL) {
            fprintf(stderr, "内存分配失败\n");
            return 1;
        }
    
        // 使用分配的内存
        for (int i = 0; i < size; i++) {
            arr[i] = i * 2;
        }
    
        // 释放内存
        free(arr);
    
        return 0;
    }
    
  2. calloc 示例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr;
        int size = 5;
    
        // 使用 calloc 分配内存,并初始化为零
        arr = (int*)calloc(size, sizeof(int));
    
        if (arr == NULL) {
            fprintf(stderr, "内存分配失败\n");
            return 1;
        }
    
        // 使用分配的内存
        for (int i = 0; i < size; i++) {
            printf("%d ", arr[i]);  // 输出应该是全零
        }
    
        // 释放内存
        free(arr);
    
        return 0;
    }
    
  3. realloc 示例:

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        int *arr;
        int size = 5;
    
        // 使用 malloc 分配内存
        arr = (int*)malloc(size * sizeof(int));
    
        if (arr == NULL) {
            fprintf(stderr, "内存分配失败\n");
            return 1;
        }
    
        // 使用分配的内存
        for (int i = 0; i < size; i++) {
            arr[i] = i * 2;
        }
    
        // 重新调整内存块的大小
        size = 10;
        arr = (int*)realloc(arr, size * sizeof(int));
    
        if (arr == NULL) {
            fprintf(stderr, "内存重新调整失败\n");
            return 1;
        }
    
        // 使用重新调整大小后的内存
        for (int i = 0; i < size; i++) {
            printf("%d ", arr[i]);
        }
    
        // 释放内存
        free(arr);
    
        return 0;
    }
    

有关扩容问题,realloc是确定可以原地扩容和异地扩容的

realloc:

  • 原地扩容:
    • realloc 可以支持原地扩容。如果在内存管理系统中存在足够的相邻空间,realloc 将会在原有的内存块上进行扩容,而不需要移动数据。
    • 在原地扩容的情况下,realloc 返回的指针值可能与之前的指针值相同,表示在原有的内存块上进行了扩容。
  • 异地扩容:
    • 如果在原地扩容不可行(例如,相邻空间不足),realloc 可能会在其他地方分配新的内存块,将原有数据复制到新的内存块中,然后释放旧的内存块。
    • 在异地扩容的情况下,realloc 返回的指针值可能与之前的指针值不同,表示发生了内存的重新分配和数据的复制。

3.2 C++内存分布

在哪里,sizeof, strlen

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";
 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);
}
  1. 选择题:

选项: A.栈 B.堆 C.数据段 D.代码段

globalVar在哪里?____ staticGlobalVar在哪里?____

staticVar在哪里?____ localVar在哪里?____

num1 在哪里?____

答案:因为全局变量,静态变量都是在数据段,数据段AKA全局/静态区(Global/Static Memory) ,所以前三个都是在数据段,localVar局部变量存在栈上,num1是一个指针,也存在栈上,所以答案是(左到右)CCCAA

char2在哪里?____ *char2在哪里?___

pChar3在哪里?____ *pChar3在哪里?____

ptr1在哪里?____ *ptr1在哪里?____

答案:char2是一个指针,存在栈上,这里的char2是一个字符数组,在栈上分配,是可修改的

  • *char2 中,char2 是一个数组,*char2 表示数组的第一个元素。
  • *pChar3 中,pChar3 是一个指针,*pChar3 表示指针指向的内存位置的内容。

最终答案是AAADAB

  1. 填空题:

sizeof(num1) = ____; sizeof(char2) = ____; strlen(char2) = ____;

sizeof(pChar3) = ____; strlen(pChar3) = ____;

sizeof(ptr1) = ____;

数组开了10个空间,10个int,那么就是40 ;char2是个字符数组,大小是5;strlen长度的话是4,不算最后的'\0';pchar3是一个指针,所以是4/8,看是32位还是64位;pchar3的strlen长度的话是4;ptr1是一个指针,大小也是4/8

40 ; 5; 4;4/8;4;4/8

sizeofstrlen 是C/C++中两个不同的操作符,用于获取信息,但它们有不同的用途和行为

sizeof:

  1. 用途:

    • sizeof 用于获取某个类型或变量所占用的字节数。
    • 对于数组,sizeof 返回整个数组占用的字节数。
    • 对于指针,sizeof 返回指针本身的字节数。
  2. 示例:

    int arr[5];
    size_t sizeOfArray = sizeof(arr);  // 返回整个数组占用的字节数
    
    int* ptr;
    size_t sizeOfPointer = sizeof(ptr);  // 返回指针本身占用的字节数
    

strlen:

  1. 用途:

    • strlen 用于获取以 null 结尾的字符串的长度,即字符串中的字符个数,不包括 null 终止符。
    • 适用于字符数组或以指针指向的字符串。
  2. 示例:

    const char str[] = "Hello";
    size_t length = strlen(str);  // 返回字符串的长度,不包括 null 终止符
    
  3. 注意:

    • strlen 需要在 null 终止的字符串上进行操作,如果字符串没有 null 终止符,可能导致未定义的行为。
    • strlen 返回的是字符串的实际长度,而不是占用的内存空间。

区别总结:

  • sizeof 是一个运算符,用于获取类型或变量的大小(以字节为单位)。
  • strlen 是一个函数,用于获取以 null 结尾的字符串的长度(字符数)。
  • sizeof 适用于任何类型或变量,包括数组、结构、指针等。
  • strlen 适用于以 null 结尾的字符串。

总体而言,sizeof 用于获取内存占用的大小,而 strlen 用于获取字符串的实际长度。在使用时需要根据具体的需求选择合适的操作符。

3.3 new 和 delete

内存管理new:delete.png

new/malloc 有底层实现机制关联交叉,不匹配使用,可能有问题,可能没问题,建议匹配使用

**malloc/free ** 和 new/delete的区别 malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符

  2. malloc申请的空间不会初始化,new可以初始化

  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可

  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

往 用法 + 底层答即可,6是重点

3.4 定位new

定位new是 在已分配的原始内存空间中调用构造函数初始化一个对象

格式 new (place_address) type或者**new (place_address) type(initializer-list)*place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景:

定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化

class Test
{
public:
 Test()
 : _data(0)
 {
 cout<<"Test():"<<this<<endl;
 }
 ~Test()
 {
 cout<<"~Test():"<<this<<endl;
 }
 
private:
 int _data;
};
void Test()
{
 // pt现在指向的只不过是与Test对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
 Test* pt = (Test*)malloc(sizeof(Test));
 
 new(pt) Test; // 注意:如果Test类的构造函数有参数时,此处需要传参
}

定位 new 主要用于以下情况:

  1. 在预分配的内存区域上构造对象: 定位 new 允许程序员在预分配的内存空间上构造对象,而不是使用默认的堆分配。这对于特殊的内存管理需求很有用,例如在嵌入式系统或硬实时系统中。
  2. 对象的放置 new 运算符: 定位 new 可以用于实现自定义的内存管理策略。程序员可以控制对象被放置的内存位置,以满足特殊的性能或硬件要求。
  3. 实现自定义的内存池: 定位 new 对于实现自定义的内存池非常有用。程序员可以通过定位 new 将对象分配到预分配的内存块中,而不是使用默认的堆分配。这有助于提高内存分配的效率和灵活性。
  4. 处理特殊硬件要求: 在某些嵌入式系统或特殊硬件环境中,可能需要更精确地控制对象的内存分配和位置。定位 new 允许程序员直接管理对象的内存,以适应特定的硬件要求。
  5. 实现自定义的内存管理策略: 如果程序需要实现自定义的内存管理策略,例如内存池、自定义分配器等,定位 new 可以提供更灵活的方式来管理对象的内存。