使用 new 创建数组而不声明大小

Creating an array using new without declaring size

这已经困扰我很长一段时间了。我有一个指针。我声明了一个 int 类型的数组。

int* data;
data = new int[5];

我相信这会创建一个大小为 5 的 int 数组。因此我将能够存储从 data[0] 到 data[4] 的值。

现在我用同样的方法创建一个数组,但没有大小。

int* data;
data = new int;

我仍然能够将值存储在数据[2] 或数据[3] 中。但是我创建了一个大小为 1 的数组。这怎么可能?

我理解数据是指向数组第一个元素的指针。虽然我还没有为下一个元素分配内存,但我仍然可以访问它们。怎么样?

谢谢。

new int 只分配 1 个整数。如果您访问大于 0 的偏移量,例如data[1] 你覆盖了内存。

在 C++ 中超出数组边界是未定义的行为,因此任何事情都可能发生,包括看似可行的事情 "correctly"。

在常见系统的实际实现术语中,您可以将 "virtual" 内存视为从 0 到指针大小的大 "flat" space,并且指针是这个space.

一个进程的"virtual"内存被映射到物理内存、页面文件等。现在,如果你访问一个没有被映射的地址,或者试图写一个只读部分,你会收到错误,例如访问冲突或段错误。

但是为了提高效率,此映射是针对相当大的块完成的,例如 4KiB "pages"。进程中的分配器,例如 newdelete(或堆栈)将根据需要进一步拆分这些页面。因此访问有效页面的其他部分不太可能引发错误。

这有一个不幸的结果,即很难检测到此类越界访问、释放后使用等。在许多情况下,写入会成功,但只会损坏其他一些看似无关的对象,这可能会导致崩溃稍后,或者不正确的程序输出,所以最好非常小心 C 和 C++ 内存管理。

data = new int; // will be some virtual address
data[1000] = 5; // possibly the start of a 4K page potentially allowing a great deal beyond it 
other_int = new int[5];
other_int[10] = 10;
data[10000] = 42; // with further pages beyond, so you can really make a mess of your programs memory
other_int[10] == 42; // perfectly possible to overwrite other things in unexpected ways

C++ 提供了许多工具来提供帮助,例如 std::stringstd::vectorstd::unique_ptr,通常最好尽量避免手动 newdelete完全。

通常情况下,new不需要分配数组"manually"。使用 std::vector<int> 更方便也更安全。并将动态内存管理的正确实现留给标准库的作者。

std::vector<int> 可选地通过 at() 方法提供带有边界检查的元素访问。

示例:

#include <vector>
int main() {
    // create resizable array of integers and resize as desired
    std::vector<int> data; 
    data.resize(5);
    // element access without bounds checking
    data[3] = 10;
    // optionally: element access with bounds checking
    // attempts to access out-of-range elements trigger runtime exception
    data.at(10) = 0; 
}

C++ 中的默认模式通常允许用未定义的行为搬起石头砸自己的脚,正如您在案例中看到的那样。

供参考:


此外,在第二种情况下,您根本没有分配数组,而是分配了一个对象。请注意,您也必须使用匹配的 delete 运算符。

int main() {
    // allocate and deallocate an array
    int *arr = new int[5];
    delete[] arr;
    // allocate and deallocate a single object
    int *p = new int;
    delete p;
}

供参考:

int * 是一个指向可能是 int 的指针。当您使用 new int 分配时,您正在分配 one int 并将地址存储到指针。实际上,int * 只是指向一些内存的指针。

我们可以将 int * 视为指向标量元素(即 new int)或元素数组的指针——语言无法告诉您指针真正指向的是什么;停止使用指针并仅使用标量值和 std::vector.

的一个很好的论据

当你说a[2]时,你很好地访问了a指向的值之后的内存sizeof(int)。如果 a 指向一个标量值,则任何东西都可能在 a 之后并且读取它会导致未定义的行为(您的程序实际上可能会崩溃——这是一个实际的风险)。写到那个地址很可能会引起问题;这不仅是一种风险,而且是您应该积极防范的事情——即,如果您需要数组,请使用 std::vector,如果不需要,请使用 intint&

当您使用 new int 然后访问 data[i] 时,i!=0 具有未定义的行为。 但这并不意味着操作会立即失败(或每次甚至永远)。 在大多数体系结构上,很可能恰好超出您要求的块末尾的内存地址已映射到您的进程,您可以访问它们。 如果您不给他们写信,那么您可以访问它们也就不足为奇了(尽管您不应该这样做)。 即使你写给他们,大多数内存分配器都有最小分配,并且在幕后你很可能已经分配 space 更多(4 个是现实的)整数,即使代码只请求 1。 您也可能会覆盖某些内存区域,但永远不会被绊倒。超出数组末尾写入的一个常见后果是破坏空闲内存存储本身。结果可能是灾难,但可能只会在以后分配类似大小的对象时表现出来。

依靠这种行为是一个可怕的想法,但它似乎有效并不奇怪。 C++ 不会(通常或默认情况下)执行严格的范围检查,并且访问无效的数组元素可能有效或至少最初看起来有效。

这就是为什么 C 和 C++ 会受到奇怪和间歇性错误的困扰。并非所有引发未定义行为的代码在每次执行时都会灾难性地失败。

表达式a[b],其中一个操作数是指针,是*(a+b)的另一种写法。为了理智起见,我们假设 a 是这里的指针(但由于加法是可交换的,所以可以反过来!试试看!);然后 a 中的地址递增 bsizeof(*a),导致 *a.

之后第 b 个对象的地址

结果指针被取消引用,导致地址为 a+b 的对象的 "name"。

注意 a 不一定是数组;如果它是一个,它 "decays" 指向在应用运算符 [] 之前的指针。该操作正在 类型化指针上进行。 如果该指针无效,或者如果 a+b 处的内存实际上不包含 [=17] 类型的对象=],或者即使该对象与 *a 无关(例如,因为它不在同一个数组或结构中),行为也是未定义的。

在现实世界中,"normal" 程序不进行任何边界检查,只是简单地将偏移量添加到指针并访问该内存位置。 (当然,访问越界内存是 C 和 C++ 中较常见的错误之一,也是这些语言并非没有限制的原因之一,建议用于高安全性应用程序。)

如果索引 b 很小,则您的程序可能可以访问该内存。对于像 int 这样的普通旧数据,最有可能的结果是您只需在该位置读取或写入内存。这就是发生在你身上的事情。

由于您覆盖了不相关的数据(这些数据实际上可能被程序中的其他变量使用),因此在更复杂的程序中,结果往往令人惊讶。此类错误很难发现,并且有工具可以检测此类越界访问。

对于较大的索引,您最终会进入未分配给您的程序的内存中,导致 Windows NT 及更高版本等现代系统立即崩溃,并且在没有的体系结构上会出现不可预测的结果内存管理。

I am still able to store values in data[2] or data[3]. But I created an array of size 1. How is this possible?

程序的行为未定义。

此外,您没有创建大小为 1 的数组,而是创建了一个非数组对象。区别很微妙。