一、内存的组织方式
程序员编写完程序之后,程序要先加载在计算机的内存中,再运行程序。在 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;
}
五、动态分配内存的基本原则
- 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大的内存块的系统开销大;
- 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵循谁分配,谁释放原则),否则可能出现内存泄露;
- 总是确保释放以分配过的内存。在编写分配内存的代码时,就需要确定在代码的什么地方释放内存;
- 在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环分配内存时,要特别小心。