使用 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"。进程中的分配器,例如 new
和 delete
(或堆栈)将根据需要进一步拆分这些页面。因此访问有效页面的其他部分不太可能引发错误。
这有一个不幸的结果,即很难检测到此类越界访问、释放后使用等。在许多情况下,写入会成功,但只会损坏其他一些看似无关的对象,这可能会导致崩溃稍后,或者不正确的程序输出,所以最好非常小心 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::string
、std::vector
和 std::unique_ptr
,通常最好尽量避免手动 new
和 delete
完全。
通常情况下,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++ 中的默认模式通常允许用未定义的行为搬起石头砸自己的脚,正如您在案例中看到的那样。
供参考:
- 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
- Undefined, unspecified and implementation-defined behavior
- What are all the common undefined behaviours that a C++ programmer should know about?
此外,在第二种情况下,您根本没有分配数组,而是分配了一个对象。请注意,您也必须使用匹配的 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
- How does delete[] know it's an array?
int *
是一个指向可能是 int
的指针。当您使用 new int
分配时,您正在分配 one int 并将地址存储到指针。实际上,int *
只是指向一些内存的指针。
我们可以将 int *
视为指向标量元素(即 new int)或元素数组的指针——语言无法告诉您指针真正指向的是什么;停止使用指针并仅使用标量值和 std::vector
.
的一个很好的论据
当你说a[2]
时,你很好地访问了a
指向的值之后的内存sizeof(int)
。如果 a
指向一个标量值,则任何东西都可能在 a
之后并且读取它会导致未定义的行为(您的程序实际上可能会崩溃——这是一个实际的风险)。写到那个地址很可能会引起问题;这不仅是一种风险,而且是您应该积极防范的事情——即,如果您需要数组,请使用 std::vector
,如果不需要,请使用 int
或 int&
。
当您使用 new int
然后访问 data[i]
时,i!=0
具有未定义的行为。
但这并不意味着操作会立即失败(或每次甚至永远)。
在大多数体系结构上,很可能恰好超出您要求的块末尾的内存地址已映射到您的进程,您可以访问它们。
如果您不给他们写信,那么您可以访问它们也就不足为奇了(尽管您不应该这样做)。
即使你写给他们,大多数内存分配器都有最小分配,并且在幕后你很可能已经分配 space 更多(4 个是现实的)整数,即使代码只请求 1。
您也可能会覆盖某些内存区域,但永远不会被绊倒。超出数组末尾写入的一个常见后果是破坏空闲内存存储本身。结果可能是灾难,但可能只会在以后分配类似大小的对象时表现出来。
依靠这种行为是一个可怕的想法,但它似乎有效并不奇怪。
C++ 不会(通常或默认情况下)执行严格的范围检查,并且访问无效的数组元素可能有效或至少最初看起来有效。
这就是为什么 C 和 C++ 会受到奇怪和间歇性错误的困扰。并非所有引发未定义行为的代码在每次执行时都会灾难性地失败。
表达式a[b]
,其中一个操作数是指针,是*(a+b)
的另一种写法。为了理智起见,我们假设 a
是这里的指针(但由于加法是可交换的,所以可以反过来!试试看!);然后 a
中的地址递增 b
倍 sizeof(*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 的数组,而是创建了一个非数组对象。区别很微妙。
这已经困扰我很长一段时间了。我有一个指针。我声明了一个 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"。进程中的分配器,例如 new
和 delete
(或堆栈)将根据需要进一步拆分这些页面。因此访问有效页面的其他部分不太可能引发错误。
这有一个不幸的结果,即很难检测到此类越界访问、释放后使用等。在许多情况下,写入会成功,但只会损坏其他一些看似无关的对象,这可能会导致崩溃稍后,或者不正确的程序输出,所以最好非常小心 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::string
、std::vector
和 std::unique_ptr
,通常最好尽量避免手动 new
和 delete
完全。
通常情况下,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++ 中的默认模式通常允许用未定义的行为搬起石头砸自己的脚,正如您在案例中看到的那样。
供参考:
- 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
- Undefined, unspecified and implementation-defined behavior
- What are all the common undefined behaviours that a C++ programmer should know about?
此外,在第二种情况下,您根本没有分配数组,而是分配了一个对象。请注意,您也必须使用匹配的 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
- How does delete[] know it's an array?
int *
是一个指向可能是 int
的指针。当您使用 new int
分配时,您正在分配 one int 并将地址存储到指针。实际上,int *
只是指向一些内存的指针。
我们可以将 int *
视为指向标量元素(即 new int)或元素数组的指针——语言无法告诉您指针真正指向的是什么;停止使用指针并仅使用标量值和 std::vector
.
当你说a[2]
时,你很好地访问了a
指向的值之后的内存sizeof(int)
。如果 a
指向一个标量值,则任何东西都可能在 a
之后并且读取它会导致未定义的行为(您的程序实际上可能会崩溃——这是一个实际的风险)。写到那个地址很可能会引起问题;这不仅是一种风险,而且是您应该积极防范的事情——即,如果您需要数组,请使用 std::vector
,如果不需要,请使用 int
或 int&
。
当您使用 new int
然后访问 data[i]
时,i!=0
具有未定义的行为。
但这并不意味着操作会立即失败(或每次甚至永远)。
在大多数体系结构上,很可能恰好超出您要求的块末尾的内存地址已映射到您的进程,您可以访问它们。
如果您不给他们写信,那么您可以访问它们也就不足为奇了(尽管您不应该这样做)。
即使你写给他们,大多数内存分配器都有最小分配,并且在幕后你很可能已经分配 space 更多(4 个是现实的)整数,即使代码只请求 1。
您也可能会覆盖某些内存区域,但永远不会被绊倒。超出数组末尾写入的一个常见后果是破坏空闲内存存储本身。结果可能是灾难,但可能只会在以后分配类似大小的对象时表现出来。
依靠这种行为是一个可怕的想法,但它似乎有效并不奇怪。 C++ 不会(通常或默认情况下)执行严格的范围检查,并且访问无效的数组元素可能有效或至少最初看起来有效。
这就是为什么 C 和 C++ 会受到奇怪和间歇性错误的困扰。并非所有引发未定义行为的代码在每次执行时都会灾难性地失败。
表达式a[b]
,其中一个操作数是指针,是*(a+b)
的另一种写法。为了理智起见,我们假设 a
是这里的指针(但由于加法是可交换的,所以可以反过来!试试看!);然后 a
中的地址递增 b
倍 sizeof(*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 的数组,而是创建了一个非数组对象。区别很微妙。