【C++入门到精通】C++入门 —— 内存管理(new函数的讲解)上

简介: 【C++入门到精通】C++入门 —— 内存管理(new函数的讲解)上

前言


       前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象。也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C和C++比较重要的知识点——内存管理。下面话不多说坐稳扶好咱们要开车了😍


一、C/C++内存分布


        C/C++的内存分布较为复杂,大概分为五个部分它们分别是:栈(Stack)、堆(Heap)、全局区/静态区(Global Area/Static Area)、常量区(Constant Area)、代码区(Code Area)下面我会分别介绍他们各自的属性。


1. 栈(Stack)


🍟栈是用来存储函数的局部变量、函数参数和函数调用的返回地址等信息的内存空间。

🍟栈空间由编译器自动管理,它的分配和释放是自动进行的,不需要程序员手动操作。

🍟每当一个函数被调用时,编译器会在栈上为该函数分配一块特定大小的空间,称为函数帧或活动记录(Activation Record)。

🍟栈是一种高效的存储方式,由于栈上的内存是连续分配的,因此访问速度较快。然而,栈的大小是有限的,过多的栈帧可能导致栈溢出的错误。


2. 堆(Heap)


🍁堆是用来动态分配内存的空间,通过使用malloc、free、new、delete等操作来分配和释放内存。

🍁堆的分配是手动进行的,程序员需要显式地分配和释放内存。

🍁堆是一种灵活的存储方式,允许动态调整分配的内存大小。

🍁堆上的内存分配通常是不连续的,因此访问速度稍慢于栈。

🍁如果使用不当,堆上的内存可能导致内存泄漏或内存碎片化等问题。


3. 全局区/静态区(Global Area/Static Area)


🍔全局区用来存储全局变量和静态变量。

🍔全局变量在程序开始执行时分配,在程序结束时被释放,因此它们的生命周期与整个程序的运行周期相同。

🍔静态变量有不同的存储位置和生命周期,具体取决于其作用域和存储类型(例如静态局部变量、静态全局变量)。

🍔全局区的内存空间在程序启动时由操作系统分配,并在程序退出时由操作系统释放。


4. 常量区(Constant Area)


🍪常量区用来存储常量数据,例如字符串字面量和其他常量。

🍪常量区的内存通常是只读的,因此程序不能对其进行修改。

🍪常量区的数据在程序运行期间保持不变。


5. 代码区(Code Area)


🥝代码区用来存储程序的机器指令,也称为可执行代码。

🥝代码区通常是只读的,存储程序的执行逻辑。

🥝代码区的内存空间在程序启动时被分配,并且在程序运行期间保持不变。


e900c8a9001b0b7d924543a38cd4e384_68b9dc8a8a814f08bdafd4190a9ea669.png


        🚨注意:具体的内存布局和分配方式可能因编译器、操作系统和硬件平台的不同而有所差异。此外,在多线程和多进程的情况下,还需要考虑线程栈、共享内存等特殊情况。


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


1. malloc函数


  • malloc 函数用于动态地分配指定大小的内存空间。
  • 它接受一个参数,即需要分配的内存大小(以字节为单位),并返回所分配内存的起始地址。
int ptr = (int)malloc(sizeof(int));
  • malloc 函数在堆上找到足够大的连续内存空间进行分配,如果找不到足够大的连续空间,则返回 NULL
  • 分配的内存是未初始化的,即其中的数据是不确定的,需要手动初始化。
#include <stdio.h>
#include <stdlib.h>
int main() {
    int size;
    int* arr;
    printf("Enter the size of the array: ");
    scanf("%d", &size);
    // 使用malloc函数动态分配内存
    arr = (int*)malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed!");
        return 1;
    }
    printf("Enter %d elements:\n", size);
    for (int i = 0; i < size; i++) {
        scanf("%d", &arr[i]);
    }
    printf("The elements you entered are: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    // 释放已分配的内存
    free(arr);
    return 0;
}


上面的程序实现了以下步骤:


  1. 用户输入数组的大小。
  2. 使用 malloc 函数动态分配足够大的内存空间来存储整数数组。
  3. 检查内存分配是否成功,如果分配失败,则输出错误信息并结束程序。
  4. 用户输入数组的元素。
  5. 打印用户输入的数组元素。
  6. 使用 free 函数释放分配的内存空间。


2. calloc函数


  • calloc 函数用于动态地分配指定数量和大小的内存空间,并将其初始化为0。
  • 它接受两个参数,第一个是需要分配的元素数量,第二个是每个元素的大小(以字节为单位)。
int ptr = (int)calloc(5, sizeof(int));
  • calloc 函数在堆上找到足够大的内存空间进行分配,并将所有字节初始化为0。
  • 分配的内存是初始化过的,不需要手动初始化。
#include <stdio.h>
#include <stdlib.h>
int main() {
    int size;
    int* arr;
    printf("Enter the size of the array: ");
    scanf("%d", &size);
    // 使用calloc函数动态分配内存,并将内存初始化为0
    arr = (int*)calloc(size, sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed!");
        return 1;
    }
    printf("Enter %d elements:\n", size);
    for (int i = 0; i < size; i++) {
        scanf("%d", &arr[i]);
    }
    printf("The elements you entered are: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    // 释放已分配的内存
    free(arr);
    return 0;
}


       上面的代码功能跟上面 malloc 函数的示例表达的功能一样这里不做过多的解释,记得在完成使用动态分配的内存后,使用 free 函数释放已分配的内存,以免出现内存泄漏。


3. realloc函数


  • realloc 函数用于重新调整之前分配的内存空间的大小。
  • 它接受两个参数,第一个是之前分配内存的起始地址,第二个是需要调整的新大小(以字节为单位)
ptr = (int*)realloc(ptr, 10 * sizeof(int));
  • realloc 函数将尝试在原始内存空间上重新调整大小,如果成功,则返回调整后的内存地址;如果原始内存空间不够大或者其他错误,则返回 NULL
  • 若新大小大于原始大小,则新增的字节未初始化;若新大小小于原始大小,则超出新大小的部分将被丢弃。
#include <stdio.h>
#include <stdlib.h>
int main() {
    int size;
    int* arr;
    printf("Enter the size of the array: ");
    scanf("%d", &size);
    // 使用malloc函数动态分配内存
    arr = (int*)malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed!");
        return 1;
    }
    printf("Enter %d elements:\n", size);
    for (int i = 0; i < size; i++) {
        scanf("%d", &arr[i]);
    }
    // 重新分配内存,将数组大小调整为10
    int newSize = 10;
    int* newArr = (int*)realloc(arr, newSize * sizeof(int));
    if (newArr == NULL) {
        printf("Memory reallocation failed!");
        free(arr);  // 释放之前分配的内存
        return 1;
    }
    // 更新指针arr的引用
    arr = newArr;
    // 打印数组元素
    printf("The elements you entered are: ");
    for (int i = 0; i < newSize; i++) {
        printf("%d ", arr[i]);
    }
    // 释放分配的内存
    free(arr);
    return 0;
}


        首先使用 malloc 函数动态分配内存,并在之后的 realloc 操作中进行了适当的错误处理。如果 realloc 函数返回 NULL ,表示内存调整失败,此时会输出错误信息并释放之前分配的内存。如果 realloc 函数成功,则将新分配的内存地址赋给 arr 指针,并继续使用调整后的内存。在最后,使用 free 函数释放分配的内存空间,并返回 0 表示程序正常结束


4. free函数


  • free 函数用于释放之前动态分配的内存空间。
  • 传入 free 函数的参数是之前分配的内存块的起始地址。
  • 调用free函数将释放所指定的内存空间,使其可供之后的malloc、calloc、realloc等函数重新使用。

       这些动态内存管理函数提供了灵活的内存分配和释放能力,可以根据需要动态调整内存的大小。然而,需要确保正确使用,避免内存泄漏和野指针等问题。建议在每次分配内存后检查分配是否成功,以及在适当的时候使用 free 函数释放不再需要的内存。


void Test ()
{
    int* p1 = (int*) malloc(sizeof(int));
    free(p1);
    int* p2 = (int*)calloc(4, sizeof (int));
    int* p3 = (int*)realloc(p2, sizeof(int)*10);
    // 这里需要free(p2)吗?  答:不用
    free(p3 );
}

       上面的代码中不需要 free(p2) 因为当调用realloc函数时,如果成功调整了内存大小,那么原始内存区域的内容将被复制到新分配的内存,并且原始的内存空间会被释放。因此,在这种情况下,不需要再手动调用 free(p2) 来释放原始的内存空间,因为realloc函数已经负责处理了。


目录
相关文章
|
19天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
38 4
SpringBoot入门(4) - 添加内存数据库H2
|
21天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
29 2
SpringBoot入门(4) - 添加内存数据库H2
|
14天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
54 13
|
8天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
24 4
|
27天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
数据安全/隐私保护 Windows
安装 Windows Server 2019
安装 Windows Server 2019
|
1月前
|
Windows
安装 Windows Server 2003
安装 Windows Server 2003
|
1月前
|
NoSQL Shell MongoDB
Windows 平台安装 MongoDB
10月更文挑战第10天
39 0
Windows 平台安装 MongoDB
|
1月前
|
Windows Python
Windows安装dlib,遇到问题汇总解决
Windows安装dlib,遇到问题汇总解决
31 4
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6