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

Creating an array using new without declaring size

本文关键字:声明 new 创建 数组 使用      更新时间:2023-10-16

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

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

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

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

int* data;
data = new int;

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

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

谢谢。

通常,不需要使用new"手动"分配数组。使用CCD_ 2更方便,也更安全。并将动态内存管理的正确实现留给标准库的作者。

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++中的默认模式通常是允许用未定义的行为射中自己的脚,正如您在案例中看到的那样。

供参考:

  • https://en.cppreference.com/w/cpp/container/vector
  • https://en.cppreference.com/w/cpp/container/vector/at
  • https://en.cppreference.com/w/cpp/language/ub
  • 未定义、未指定和实现定义的行为
  • 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;
}

供参考:

  • https://en.cppreference.com/w/cpp/language/new
  • https://en.cppreference.com/w/cpp/language/delete
  • delete[]是如何知道的';是数组吗

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

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

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

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

在常见系统的实际实现术语中,您可以将"虚拟"内存视为一个从0到指针大小的大"平面"空间,指针位于该空间中。

进程的"虚拟"内存被映射到物理内存、页面文件等。现在,如果您访问未映射的地址,或者试图写入只读部分,则会出现错误,例如访问冲突或segfault。

但为了提高效率,这种映射是针对相当大的块进行的,例如针对4KiB"页面"。进程中的分配器,如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 int只分配1个整数。如果访问的偏移量大于0,例如data[1],则会覆盖内存。

int *是指向可能是int的东西的指针。当您使用std::vector<int>0进行分配时,您将分配一个int并将地址存储到指针。实际上,int *只是一个指向某些内存的指针。

我们可以将int *视为指向标量元素(即new int)或元素数组的指针——语言无法告诉您指针真正指向的是什么;停止使用指针并仅使用标量值和CCD_ 23是一个非常好的参数。

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

表达式a[b]是另一种写入*(a+b)的方法,其中一个操作数是指针。为了保持理智,我们假设a是这里的指针(但由于加法是交换的,它可以是另一种方式!试试看!);则a中的地址增加b乘以sizeof(*a),得到*a之后的第b个对象的地址。

结果指针被取消引用,从而为地址为a+b的对象生成一个"名称"。

注意,a不必是一个数组;如果它是一,则在应用运算符[]之前,它"衰减"到一个指针。操作发生在键入的指针上如果该指针无效,或者a+b处的内存实际上没有保存*a类型的对象,或者即使该对象与*a无关(例如,因为它不在同一数组或结构中),则行为是未定义的。

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

如果索引b很小,那么您的程序可能可以访问内存。对于像int这样的普通旧数据,最有可能的结果是您只需读取或写入该位置的内存。这就是你的遭遇。

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

对于较大的索引,您将在某个时刻进入未分配给程序的内存,这将导致Windows NT及以上现代系统立即崩溃,并且在没有内存管理的体系结构上产生不可预测的结果。

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

程序的行为未定义。

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