我知道在C ++中无法获得动态创建的数组的大小,例如:
int* a;
a = new int[n];
我想知道的是:为什么?人们是否在C ++规范中忘记了这一点,还是有技术原因?
不是存储在某个地方的信息吗?毕竟,命令
delete[] a;
似乎知道它必须释放多少内存,所以在我看来delete[]
有一些方法可以知道a
的大小。
答案 0 :(得分:38)
这是从“不为你不需要付出代价”这一基本规则的后续行动。在您的示例中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 ++库无法知道{{}返回的块的大小1}}。 (通常,malloc
和new
都是同一个库的一部分 - 但并非总是如此。)
答案 1 :(得分:14)
一个基本原因是,指向动态分配的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
,但要实现此目的,您需要添加a"尺寸标签"在每个对象上
char
在64位计算机上需要128位存储空间(因为对齐)。这比必要的多十六倍
(上面,十个字符的数组a
至少需要168个字节。)
这可能很方便,但也是不可接受的昂贵。
当然,如果参数确实是默认operator new
动态分配的第一个元素的指针,那么您当然可以设想一个只有明确定义的版本,但这并不是很有用人们可能会想到。
答案 2 :(得分:4)
你是对的,系统的某些部分必须知道有关尺寸的信息。但是,内存管理系统的API可能无法获取该信息(请考虑mod_ping
/ malloc
),并且可能无法知道您请求的确切大小,因为它可能已被四舍五入。
答案 3 :(得分:2)
有一个奇怪的例子,我以found的形式超载operator delete
:
void operator delete[](void *p, size_t size);
参数 size 似乎默认为void * p指向的内存块的大小(以字节为单位)。如果这是真的,至少希望它具有operator new
的调用所传递的值是合理的,因此,只需要将 sizeof(type)除以提供存储在数组中的元素数量。
至于你问题的“为什么”部分,Martin的“不为你不需要付出代价”的规则似乎是最合乎逻辑的。
答案 4 :(得分:2)
您经常会发现内存管理器只会分配特定倍数的空间,例如64字节。
因此,您可能会要求新的int [4],即16个字节,但内存管理器将为您的请求分配64个字节。要释放这个内存,它不需要知道你要求多少内存,只需要分配一个64字节的块。
下一个问题可能是,它是否可以存储请求的大小?这是一个额外的开销,并不是每个人都愿意为此付出代价。例如,Arduino Uno只有2k的RAM,在这种情况下,每个分配的4个字节突然变得很重要。
如果您需要该功能,那么您拥有std :: vector(或等效的),或者您拥有更高级别的语言。 C / C ++旨在使您能够以您选择使用的开销来处理,这是一个例子。
答案 5 :(得分:1)
无法知道如何使用该阵列。 分配大小不一定与元素编号匹配,因此您不能仅使用分配大小(即使它可用)。
这是其他语言的一个深层缺陷,而不是C ++。 您可以使用std :: vector实现所需的功能,但仍保留对数组的原始访问权限。保留原始访问权对于任何实际必须执行某些工作的代码至关重要。
很多时候,您将对数组的子集执行操作,当您在语言中内置了额外的簿记时,您必须重新分配子数组并将数据复制出来以使用需要托管数组的API。
只考虑排序数据元素的陈腐案例。 如果你有托管数组,那么你不能在不复制数据的情况下使用递归来创建新的子数组来递归传递。
另一个例子是一个FFT,它递归地操作以2x2“蝴蝶”开始的数据,然后回到整个数组。
要修复托管数组,您现在需要“其他东西”来修补此缺陷,并将“其他内容”称为“迭代器”。 (您现在拥有托管数组,但几乎从未将它们传递给任何函数,因为您需要90%的时间使用迭代器。)
答案 6 :(得分:-2)
分配有new[]
的数组的大小不会明显存储在任何地方,因此您无法访问它。并且new[]
运算符不返回数组,只是指向数组的第一个元素的指针。如果您想知道动态数组的大小,则必须手动存储它或使用库中的类,例如std::vector