我知道C++中没有办法获得一个动态创建的数组的大小,例如:
1 2
| int* a;
a = new int[n]; |
我想知道的是:为什么?在C++规范中,人们是否忘记了这一点,或者是因为技术原因吗?
信息不是存储在某个地方吗?毕竟,命令
号
似乎知道它必须释放多少内存,所以在我看来,delete[]有某种方法可以知道a的大小。
- 动态数组是C++的错误设计。这些设施由图书馆解决方案(如std::vector)提供得更好。(当然,模板只添加到C++后面,而不是EDCOX1〔1〕,所以只能用事后诸葛亮来说明。)
- delete[] a不一定比free(p)更需要知道尺寸。你可能需要知道大小的唯一原因是如果你需要调用析构函数,但是对于int没有这样的需求。
- 当C++被设计时,编译器必须使用不太多的资源。在这种情况下,C++编译器是一个很好的艺术品。现在,没有理由这么做,但是C++编译器一点都没有改变。
- @好的,那么为什么free(p)不需要知道需要释放多少内存(从p开始)?
- @Jarauh:实际的原因是许多分配器聚集了分配的大小。
- @Jarauh:它需要知道需要释放多少内存,但不需要知道内存表示多少"对象",因为C没有要调用的析构函数。
- C++允许您从一开始就在任何级别构建软件。大多数人不应该使用原始数组。大多数人应该使用std::vector。编译器和库设计器等…仍然需要灵活性来决定如何存储大小信息,而不需要语言本身强制采用特定的方式。
- @Jarauh 1号。目前有一个关于这一点的讨论是comp.std.c。malloc/free可能只是调用系统分配器,而系统分配器不允许用户代码找出分配的块大小。2。free()可能知道malloc()为您提供了64个字节,但它不知道这是因为您要求1 int还是16。
- 我在这里有点学究,但实际上获取动态数组的大小很简单:如果需要字节,可以使用n或n * sizeof(*a)。我认为您的意思是说,无法仅使用指向动态分配数组的指针来获取该数组的大小。
- sizeof(*a)将是4,因为类型为(*a)是int…
- @Martinbonner和Msalters谢谢你,这似乎是我想要的答案。请写这个作为答案,这样我可以接受。
- @亮度RaceSinorbit可以,但是如果内存总是numberOfObjects*sizeOfObjects,就足以知道总内存。但显然,记忆可能更大。
- @Aconcagua:sizeof(int)并不总是4。
- 为什么C数组不跟踪它们的长度?
- @轻量级的赛马Sinorbit:事实,我知道。4只是最常见的情况。实际上,我已经看过sizeof(int)==2甚至是1次了。我真正想说明的是sizeof(*a)不是数组的sizeof,而是它的第一个元素的sizeof…不过,最好写==sizeof(int)。
- @Jarauh:由系统分配器来跟踪这些信息。用户不必这样做。
- 为什么语言需要一个设施来(1)告诉你你已经知道的东西:"它是n"或者(2)纠正你的对象中的设计失败:"如果你想将这个数字与这个对象关联,为什么不把它存储在你能得到它的地方?"
- @ ErcStudies(2),因为它强迫你保存C++不喜欢做的冗余数据。它还要求编写锅炉板代码来传递struct T_ptr{ T*t; size_t size;},而不仅仅是产生缓存位置问题的T*t。我知道如何支持这是一个合法的设计选择。
- @FredOverflow谢谢,但是这个问题的公认答案集中在静态数组上。
- delete[]如何"知道"操作数数组的大小?
- C++直接从C语言继承了它的数组语义,在数组对象或内存的动态分配区域的任何指针中都没有存储任何数组元数据(如长度),里奇有其理由(参见第6页开始的"胚胎C"一节)。由动态内存管理器实现来跟踪与特定指针关联的内存量。在您的代码中,您知道您需要多少内存,并且您需要跟踪这些数据。
- @ L?紫外线?nhph&250;c这个问题是如何复制的?他们问不同的问题。这个问题是关于堆分配器不公开它所知道的数组大小的技术原因。这与分配器如何知道大小无关:问题已经假定分配器知道大小。
- 这不是另一个问题的副本。C++不是C,它是合法的,当EDCOX1(0)调用正确的元素个数时,为什么C++不允许你查询分配大小(显然,大小是已知的)。这与知道原始块大小的malloc或free不同(尽管不可否认这是相似的)。
这是"不要为你不需要的东西付钱"这一基本原则的后续行动。在您的示例中,delete[] a;不需要知道数组的大小,因为in t没有析构函数。如果你写过:
1 2 3 4
| std::string* a;
a = new std::string[n];
...
delete [] a; |
然后,delete必须调用析构函数(并且需要知道要调用多少个析构函数),在这种情况下,new必须保存该计数。然而,考虑到它不需要在任何情况下保存,比亚恩决定不给它访问权。
(事后看来,我认为这是个错误…)
当然,即使使用int,也必须了解所分配内存的大小,但是:
为了对齐和方便起见,许多分配器将大小归整为一些方便的倍数(比如64字节)。分配器知道一个块有64个字节长,但不知道是不是因为n是1…或16。
C++运行时库可能无法访问所分配的块的大小。例如,如果EDCOX1,2,EDCX1,1,则使用EDOCX1,7,EDCX1,8,ED,则C++库没有办法知道EDCOX1,7,7所返回的块的大小。(通常情况下,new和malloc都是同一个库的一部分,但并不总是如此。)
- 即使是int,大小也不一定要知道吗?在用于保存已分配/可用内存证据的任何结构中标记内存可用?
- 如果这是一个错误,至少这是一个向前兼容的错误。如果委员会决定要纠正它,他们可以这样做而不破坏任何东西。反过来就不成立了。
- @波洛夫不一定。分配程序可能知道分配的块的大小,但这可能不是您要求的大小;可以分配较大的块,原因包括避免碎片化或某些平台上的对齐问题。
- 当数组元素类型不容易破坏时,C++ 14需要数组删除来调用大小的解除分配函数。演示
- 我想关键是如果我们需要它,我们可以添加它。
- @Kerreksb刚刚注意到你的评论。1。在所有版本的C++中,使用数组删除来删除数组的要求都存在。2。这个要求是绝对的——即使元素类型是可破坏的(不使用数组删除是未定义的行为)。
- @马丁邦纳:这不是我所指的细节。我特别提到了作为DELISH表达式的结果的数组解除分配函数,并且对于C++ 14,有多个候选(大小与未大小)。至于C++ 17,还有更多(即对齐扩展)。
一个根本原因是指向动态分配的T数组的第一个元素的指针和指向任何其他T的指针之间没有区别。
考虑一个虚构的函数,它返回指针指向的元素数。我们称之为"尺寸"。
听起来不错,对吧?
如果不是因为所有指针都是相等的:
1 2 3 4 5 6 7 8 9
| 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,但要做到这一点,你需要在每个对象上添加一个"尺寸标签"。在64位机器上,char需要128位存储(因为对齐)。这是必要的16倍。(上面,十字符数组a至少需要168字节。)
这可能很方便,但也很贵。
当然,您可以设想一个只有在参数确实是指向默认operator new动态分配的第一个元素的指针时定义良好的版本,但这并不像人们想象的那样有用。
- 我明白这一点。我想知道的是:当数组被删除时,编译器突然似乎记住了一些关于数组大小的内容。从上面的注释中我学到的是编译器只记住保留了多少内存,这只给出了数组大小的下限。
- 更简单的解决方案是所有这些用途都是未定义的行为。可以将size()定义为仅在new[]数组上工作。
- @雅罗和以东十一〔5〕是指以东十一〔6〕对吗?
- @Jarauh尝试删除a[1]。你希望会发生什么?实际发生了什么?:)另外,为什么您认为编译器知道数组有多大?C++编译器解决了我没有查看的问题吗?D
- 你可能还想提出子数组。我可以用new生成一个10个chars的数组,然后用它作为5个chars的两个独立数组。编译器无法知道我给数组的不同部分赋予了不同的语义,因此size()函数会给出错误的值。
- @Luaan删除&a[1]可以处理琐碎的对象,前提是后备内存分配器接受其分配范围内任意位置的空闲使用指针。C++不要求这一点,并要求您从EDCOX1(8)中获得的地址中删除。
您是对的,系统的某些部分必须了解一些关于大小的信息。但是获取这些信息可能不在内存管理系统的API中(想想malloc/free),您请求的确切大小可能不知道,因为它可能已经被取整了。
例如,您经常会发现内存管理器只在特定的多个64字节中分配空间。
因此,您可以请求新的int[4],即16个字节,但是内存管理器将为您的请求分配64个字节。为了释放这个内存,它不需要知道您需要多少内存,只需要为您分配一个64字节的块。
下一个问题可能是,它不能存储请求的大小吗?这是一个额外的管理费用,不是每个人都准备支付。例如,一个arduino uno只有2K的RAM,在这种情况下,每个分配的4个字节突然变得非常重要。
如果你需要这个功能,那么你有STD::向量(或等效),或者你有高级语言。设计C/C++是为了让你能在你选择使用的时候尽可能少的开销,这是一个例子。
我发现有一个奇怪的情况,即operator delete过载,其形式如下:
1
| void operator delete[](void *p, size_t size); |
号
参数大小似乎默认为void*p指向的内存块的大小(以字节为单位)。如果这是真的,至少希望它有一个通过调用operator new传递的值是合理的,因此,只需要除以sizeof(type)就可以传递存储在数组中的元素数。
至于你问题的"为什么"部分,马丁的"不为你不需要的东西付钱"规则似乎是最合乎逻辑的。
- 您假设分配的块的大小等于数组的大小,我认为这不一定是真的。当然,它不能小一些,但我不认为有什么能阻止它变大(例如,如果分配只能通过固定的阻塞大小来完成的话)。
- C++ 14要求当数组元素类型不容易破坏时,数组删除表达式将调用此解除分配函数。
- @Kerreksb I(虚弱,除了模糊的记忆之外,什么都没有)认为它只有在构建过程中失败时才被调用?你能引述一下吗?
- @雅克:你可能在考虑安置表。普通的delete也调用dealLocation函数:—)
无法知道如何使用该数组。分配大小不一定与元素编号匹配,因此不能仅使用分配大小(即使它可用)。
这是其他语言中的一个深层缺陷,而不是C++。通过STD::vector实现了您希望的功能,但仍然保留对数组的原始访问。保留原始访问对于任何实际需要做一些工作的代码都是至关重要的。
很多时候,您将对数组的子集执行操作,当您在语言中内置了额外的簿记时,您必须重新分配子数组,并将数据复制出来,以便使用期望托管数组的API来操作它们。
只需考虑对数据元素进行排序的老套情况。如果您有托管数组,那么在不复制数据的情况下就不能使用递归来创建新的子数组来递归传递。
另一个例子是一个快速傅立叶变换,它递归地处理从2x2"蝴蝶"开始的数据,并返回整个数组。
要修复托管数组,现在需要"其他东西"来修补这个缺陷,"其他东西"称为"迭代器"。(您现在已经有了托管数组,但几乎从未将它们传递给任何函数,因为您需要90%以上的迭代器。)
与new[]一起分配的数组的大小在任何地方都不可见地存储,因此您无法访问它。new[]运算符不返回数组,只返回指向数组第一个元素的指针。如果要知道动态数组的大小,必须手动存储它,或者使用来自库(如std::vector的类)的类。
- 我想问题是为什么它是这样设计的?
- …至少在某些情况下,大小必须存储在某个地方(以便delete[]可以调用正确数量的析构函数)。
- 并非所有指针都指向数组的开头。因此,即使一个数组知道它的大小,如果您将&array[1]传递给一个函数,它也不再指向该数组,并且找不到它的大小。