动态内存

在前面章节中看到的程序里,所有内存需求都是在程序执行前通过定义所需变量来确定的。但在某些情况下,程序的内存需求只能在运行时确定。例如,当所需内存取决于用户输入时。在这些情况下,程序需要动态分配内存,为此,C++ 语言集成了 newdelete 运算符。

运算符 new 和 new[]

动态内存使用 new 运算符进行分配。new 后面跟着一个数据类型说明符,如果需要一个包含多个元素的序列,还需要在方括号 [] 中指定元素的数量。它返回一个指向新分配内存块起始位置的指针。其语法是:

pointer = new type
pointer = new type [number_of_elements]

第一个表达式用于分配内存以容纳一个 type 类型的单个元素。第二个表达式用于分配一个 type 类型的元素块(一个数组),其中 number_of_elements 是一个整数值,表示元素的数量。例如:

1
2
int * foo;
foo = new int [5];

在这种情况下,系统动态分配了可以容纳五个 int 类型元素的空间,并返回一个指向该序列第一个元素的指针,该指针被赋给 foo(一个指针)。因此,foo 现在指向一个有效的内存块,该内存块有足够的空间容纳五个 int 类型的元素。



在这里,foo 是一个指针,因此,foo 指向的第一个元素可以通过表达式 foo[0] 或表达式 *foo(两者等价)来访问。第二个元素可以通过 foo[1]*(foo+1) 来访问,依此类推……

声明一个普通数组和使用 new 为内存块动态分配内存之间存在着实质性的区别。最重要的区别是,常规数组的大小必须是一个常量表达式,因此其大小必须在程序设计时、运行之前就确定。而由 new 执行的动态内存分配允许在运行时使用任何变量值作为大小来分配内存。

我们的程序所请求的动态内存是由系统从内存堆中分配的。然而,计算机内存是有限的资源,它可能会被耗尽。因此,不能保证所有使用 new 运算符分配内存的请求都会被系统批准。

C++ 提供了两种标准机制来检查分配是否成功:

一种是通过处理异常。使用此方法,当分配失败时,会抛出一个类型为 bad_alloc 的异常。异常是 C++ 的一个强大功能,将在后面的教程中进行解释。但现在,您应该知道,如果这个异常被抛出并且没有被特定的处理程序捕获,程序执行将终止。

这种异常方法是 new 默认使用的方法,并且在像下面这样的声明中使用:

1
foo = new int [5];  // if allocation fails, an exception is thrown  

另一种方法被称为 nothrow,当使用它时,如果内存分配失败,它不会抛出 bad_alloc 异常或终止程序,而是使 new 返回的指针成为一个空指针,程序会继续正常执行。

这种方法可以通过使用一个名为 nothrow 的特殊对象来指定,该对象在头文件 <new> 中声明,作为 new 的参数:

1
foo = new (nothrow) int [5];

在这种情况下,如果这块内存分配失败,可以通过检查 foo 是否为空指针来检测到失败:

1
2
3
4
5
int * foo;
foo = new (nothrow) int [5];
if (foo == nullptr) {
  // error assigning memory. Take measures.
}

与异常相比,nothrow 方法可能会产生效率较低的代码,因为它意味着在每次分配后都要显式检查返回的指针值。因此,通常首选异常机制,至少对于关键的内存分配是这样。尽管如此,由于其简单性,接下来的大多数例子将使用 nothrow 机制。

运算符 delete 和 delete[]

在大多数情况下,动态分配的内存仅在程序内的特定时间段内需要;一旦不再需要,就可以将其释放,以便该内存可再次用于其他动态内存请求。这就是 delete 运算符的目的,其语法是:

1
2
delete pointer;
delete[] pointer;

第一条语句释放使用 new 分配的单个元素的内存,第二条语句释放使用 new 和方括号 ([]) 中的大小为元素数组分配的内存。

传递给 delete 的参数值应该是一个指向先前用 new 分配的内存块的指针,或者是一个空指针(在是空指针的情况下,delete 不产生任何效果)。

// rememb-o-matic
#include <iostream>
#include <new>
using namespace std;

int main ()
{
  int i,n;
  int * p;
  cout << "How many numbers would you like to type? ";
  cin >> i;
  p= new (nothrow) int[i];
  if (p == nullptr)
    cout << "Error: memory could not be allocated";
  else
  {
    for (n=0; n<i; n++)
    {
      cout << "Enter number: ";
      cin >> p[n];
    }
    cout << "You have entered: ";
    for (n=0; n<i; n++)
      cout << p[n] << ", ";
    delete[] p;
  }
  return 0;
}
How many numbers would you like to type? 5
Enter number : 75
Enter number : 436
Enter number : 1067
Enter number : 8
Enter number : 32
You have entered: 75, 436, 1067, 8, 32,

请注意,new 语句中方括号内的值是用户输入的变量值 (i),而不是一个常量表达式:

1
p= new (nothrow) int[i];

用户总是有可能为 i 输入一个非常大的值,以至于系统无法为其分配足够的内存。例如,当我试图为“How many numbers”问题输入 10 亿这个值时,我的系统无法为程序分配那么多内存,然后我收到了我们为此情况准备的文本消息 (Error: memory could not be allocated)。

一个良好的编程实践是,程序应始终能够处理内存分配失败的情况,无论是通过检查指针值(如果使用 nothrow)还是通过捕获相应的异常。

C 语言中的动态内存

C++ 集成了 newdelete 运算符来分配动态内存。但在 C 语言中并没有这些运算符;取而代之的是,它使用了一个库解决方案,即在头文件 <cstdlib>(在 C 中称为 <stdlib.h>)中定义的函数 malloccallocreallocfree。这些函数在 C++ 中也可用,并且同样可以用于分配和释放动态内存。

但请注意,由这些函数分配的内存块不一定与 new 返回的内存块兼容,因此不应将它们混合使用;每种分配都应该用其自己的一套函数或运算符来处理。

Index
目录