12. 内存管理

一、内存的组织方式

  程序员编写完程序之后,程序要先加载在计算机的内存中,再运行程序。在 C++ 中,不同数据在内存中所存储的位置也不一样。全局变量存储在内存中的静态存储区,非静态的局部变量存储在内存中的动态存储区(栈)。临时使用的数据建立动态内存分配区域,需要的时候开辟,不需要时及时释放(堆)。

  通过内存注释方式可以看出,堆用来存放动态分配内存空间,而栈用来存放局部数据变量、函数的参数以及调用函数与被调函数的联系。

  在内存的全局存储空间中,用于程序动态分配和释放的内存块称为自由存储空间,通常也称之为堆。在 C 程序 中,使用 malloc() 函数和 free() 函数来从堆中动态的分配内存和释放内存。在 C++ 中仍然可以这么做,但 C++ 推荐使用 new 和 delete 关键字动态管理内存。

  程序不会像处理堆那样在栈中显示地分配内存。当程序调用函数或声明局部变量时,系统将自动分配内存。

  栈是一个后进先出的压入弹出式的数据结构。在程序运行时,需要每次向栈中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,最晚进栈的对象将被弹出,然后栈指针向上移动一个位置。如果栈指针位于栈顶,则表示栈是空的;如果栈指针指向最下面的数据项的后一个位置,则表示栈为满的。

二、动态内存管理

  new 关键字根据类型来确定需要多少个字节的内存。然后,它找到这样的内存,并返回其地址。为一个数据对象(可以是结构,也可以是基本数据类型)获取并指定分配内存的通用格式如下:

类型名 * 指针变量名 = new 类型名;

  需要在两个地方指定数据类型:用来指定需要什么样的内存和用来声明合适的指针。

  new 分配的内存块通常与常规变量声明分配的内存块不同,常规变量的值都存储在栈(stack)的内存区域中,而 new 从堆中分配内存。

  当需要内存时,可以使用 new 申请内存,使用完内存后,需要使用 delete 释放内存,将其归还给内存池。使用 delete 时,后面要加上指向内存块的指针。

  我们还可以为一个指针重新指向另一个新分配的内存块。我们一定要配对的使用 new 和 delete,否则会发生内存泄露问题,也就是说,被分配的内存再也无法使用了。如果内存泄露严重,则程序将由于不断寻找更多的内存导致内存耗尽而终止。还有不要充实释放已经释放的内存块。C++ 标准指出,这样做的后果是不确定的。

#include <iostream>

using namespace std;

int main(void)
{
    int a = 10;
    int *p1 = new int;
    *p1 = a;
    int *p2 = new int(a*10);

    cout << "*p1: " << *p1 << endl;
    cout << "*p2: " << *p2 << endl;

    delete p1;
    delete p2;

    return 0;
}

只能使用 delete 释放使用 new 分配的内存。然后,对于空指针使用 delete 是安全的。

三、使用new创建动态数组

  在编译时给数组分配内存被称为 静态联编,意味着数组是在编译时加入到程序的。但使用 new 时,如果在运行阶段需要数组,则创建它,如果不需要,则不创建。还可以在程序运行时选择数组的长度。这称为 动态联编,意味着数组是在程序运行时创建的。这种数组叫作动态数组。使用静态联编时,必须在编写程序时指定数组的长度。使用动态联编时,程序将在运行时确定数组的长度。

  在 C++ 中,创建动态数组只需要将数组的元素类型和元素数目告诉 new 即可。必须在类型名后加上方括号,其中包含元素的数目。new 运算符将返回第一个元素的地址。

类型名 数组名 = new 类型名[数组大小];

  当程序使用完 new 分配的数组后,应该使用 delete 释放内存。

delete [] 数组名;

  其中,方括号告诉程序,应释放整个数组,而不仅仅是指针指向的元素。如果使用 new 时,不带方括号,则使用 delete 时,也应该不带方括号。如果使用 new 时带方括号,则使用 delete 时也应带方括号。

#include <iostream>

using namespace std;

int main(void)
{
    int length = 0;

    cout << "请输入要创建的整型数组的元素个数: " << endl;
    cin >> length;

    int * array = new int[length];
  
    cout << "请填充整型数组元素,中间用空格分隔" << endl;
    for (int i = 0; i < length; i++)
    {
        cin >> array[i];
    }

    cout << "填充后的数组为:" << endl;
    for (int i = 0; i < length; i++)
    {
        cout << array[i] << " ";
    }
    cout << endl;

    delete[] array;

    return 0;
}

四、使用new创建动态结构

  通过使用 new,可以创建动态结构。同样,动态意味着内存是在运行时,而不是编译时分配的。创建动态结构时,不能将成员运算符句点用于结构名,因为这种结构没有名称,只知道它的地址。C++ 为这种情况提供了一个运算符:箭头运算符(->)。该运算符由连字符和大于号组成,可用于指向结构指针。

#include <iostream>

using namespace std;

struct Person
{
    string name;
    int age;
};

int main(void)
{
    Person * person = new Person;

    cout << "请输入姓名:" << endl;
    cin >> person->name;

    cout << "请输入年龄:" << endl;
    cin >> person->age;

    cout << "{name: " << person->name << ", age: " << person->age << "}";

    delete person;

    return 0;
}

五、动态分配内存的基本原则

  1. 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大的内存块的系统开销大;
  2. 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵循谁分配,谁释放原则),否则可能出现内存泄露;
  3. 总是确保释放以分配过的内存。在编写分配内存的代码时,就需要确定在代码的什么地方释放内存;
  4. 在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环分配内存时,要特别小心。
posted @ 2023-04-20 20:37  星光樱梦  阅读(12)  评论(0编辑  收藏  举报