我知道在C++中没有办法获取动态创建数组的大小,例如:
int* a;
a = new int[n];
我想知道的是为什么?人们是忘了在C++的规范中加入还是由于技术原因而不包含该功能?信息不是被存储在某个地方吗?毕竟,命令
delete[] a;
似乎知道需要释放多少内存,所以我认为delete[]
有一些方法可以知道a
的大小。
我知道在C++中没有办法获取动态创建数组的大小,例如:
int* a;
a = new int[n];
我想知道的是为什么?人们是忘了在C++的规范中加入还是由于技术原因而不包含该功能?delete[] a;
似乎知道需要释放多少内存,所以我认为delete[]
有一些方法可以知道a
的大小。
这是“不要为不需要的东西付费”基本法则的衍生规则。在你的例子中,delete[] a;
不需要知道数组的大小,因为int类型没有析构函数。如果你写成:
std::string* a;
a = new std::string[n];
...
delete [] a;
那么delete
必须调用析构函数(并且需要知道要调用多少个),在这种情况下,new
必须保存该计数。但由于它并不总是需要保存,Bjarne决定不提供访问权限。
(事后看来,我认为这是一个错误...)
即使使用int
,当然也必须有某些东西知道分配的内存大小,但是:
许多分配器将大小舍入到一些方便的倍数(例如64字节)以进行对齐和方便的原因。分配器知道块的长度为64个字节,但它不知道这是因为n
是1还是16。
C ++运行时库可能无法访问已分配块的大小。例如,如果new
和delete
在底层使用malloc
和free
,则C ++库无法知道malloc
返回的块的大小。(通常,new
和malloc
都是同一库的一部分,但并非总是如此。)
一个基本的原因是,指向动态分配的T
数组的第一个元素的指针和指向任何其他T
的指针之间没有区别。
考虑一个虚构的函数,它返回指针指向的元素数量。我们将其称为“大小”。
听起来很不错,对吧?
如果不是因为所有指针都是平等的这个事实:
char* p = new char[10];
size_t ps = size(p+1); // What?
char a[10] = {0};
size_t as = size(a); // Hmm...
size_t bs = size(a + 1); // Wut?
char i = 0;
size_t is = size(&i); // OK?
9
,第二个是10
,第三个是9
,最后一个是1
,但是要实现这一点,你需要在每个对象上添加“大小标签”。char
将需要128位的存储空间(因为对齐)。这比必要的空间多16倍。a
至少需要168字节。)
这可能很方便,但代价也太高了。
当然,你可以想象一个版本,只有在参数确实是默认operator new
动态分配的第一个元素的指针时才定义良好,但这并不像人们想象的那样有用。
size()
可以被定义为仅适用于使用new[]
动态分配的数组。 - John Kugelmana[1]
。你期望会发生什么?实际上会发生什么? :) 另外,你认为 编译器 是如何知道数组的大小的?难道 C++ 编译器在我不注意的时候解决了停机问题吗? :D - Luaannew
生成一个包含 10 个 char
的数组,但是将其用作两个包含 5 个 char
的单独数组。编译器无法知道我已经为数组的不同部分赋予了不同的语义含义,因此 size()
函数将给出错误的值。 - Eldritch Cheese&a[1]
可以在支持从其分配范围内的任何位置使用指针进行释放的平凡对象的后备内存分配器的情况下工作。C++ 不要求这样做,并要求您从 new
获得的地址进行删除。 - Yakk - Adam Nevraumontp
以外的任何东西调用 delete[]
都将导致未定义行为。我们为什么期望 size
表现得与此不同呢?只需声明当针对可能被合法删除的内容调用时才定义它。即使它只返回实际内存大小而不是请求的大小,我也可以看到某些情况下这将非常有用。例如,如果您有一个向量类想要在重新分配之前知道其实际可用存储空间量。 - Joel Croteau您说得对,系统的某些部分需要知道一些关于大小的信息。但是获取这些信息可能不在内存管理系统的API(例如malloc
/free
)中,而且您请求的确切大小可能未知,因为它可能已经被舍入。
通常情况下,内存管理器只会按照一定的倍数进行内存分配,例如64字节。
因此,你可能请求分配一个新的int[4],即16字节,但是内存管理器将会为你的请求分配64字节。释放这块内存时,它不需要知道你请求了多少内存,只需要知道已经为你分配了一块64字节的内存块。
接下来的问题可能是,它不能存储所请求的大小吗?这是一种额外的开销,并非每个人都愿意承担。例如,Arduino Uno只有2k的RAM,在这种情况下,每个分配的4个字节突然变得相当重要。
如果你需要该功能,则可以使用std::vector(或等效物),或者使用更高级别的编程语言。C / C ++旨在使你能够尽可能地使用少量开销工作,这是其中之一。
无法知道您将如何使用该数组。 分配大小不一定与元素数量匹配,因此不能仅使用分配大小(即使可用)。
这是其他语言中的一个严重缺陷,而不是C ++中的。 您可以使用std :: vector实现所需的功能,但仍保留对数组的原始访问。保留该原始访问对于需要执行某些工作的任何代码都至关重要。
许多时候,您将对数组的子集执行操作,并且当您在语言中内置了额外的簿记时,您必须重新分配子数组并将数据复制出来以使用期望托管数组的API进行操作。
只需考虑排序数据元素的平凡情况。 如果您有托管数组,则无法使用递归,因为需要复制数据以创建新的子数组以递归传递。
另一个例子是FFT,它从2x2“蝴蝶”开始递归地操作数据,并向后处理整个数组。
要修复托管数组,现在需要“其他东西”来弥补这个缺陷,这个“其他东西”称为'迭代器'。(现在您有托管数组,但几乎从不将它们传递给任何函数,因为您90%以上的时间都需要迭代器。)
new[]
分配的数组大小没有被明确地存储在任何地方,因此您无法访问它。而且,new[]
运算符不会返回一个数组,只会返回指向数组第一个元素的指针。如果您想知道动态数组的大小,必须手动存储它或使用来自库(如std::vector
)的类。delete[]
可以调用正确数量的析构函数)。 - Martin Bonner supports Monica&array[1]
传递给一个函数,它就不再指向该数组,并且无法找到其大小。 - Barmar
std::vector
)。当然,模板是晚于new
添加到 C++ 中的,所以这只是从后见之明来看的。 - Kerrek SBdelete[] a
不需要比free(p)
更多地知道大小。只有在需要调用析构函数时才需要知道大小,但对于int
类型来说并没有这样的需求。 - Kerrek SB