用new分配的内存,重新分配时是否安全?

38

根据这里所写,newfree store 中分配内存而 malloc 使用 heap,这两个术语通常指的是相同的东西。

根据这里所写,realloc 可能会将内存块移动到新位置。如果 free store 和 heap 是两个不同的内存空间,那么会有问题吗?

具体而言,我想知道是否可以安全地使用它。

int* data = new int[3];
// ...
int* mydata = (int*)realloc(data,6*sizeof(int));

如果不行,那么有没有其他安全地重新分配使用new分配的内存的方法?我可以分配新的空间并使用memcpy复制内容,但据我所知,realloc可能会在可能的情况下使用相同的区域。


14
只需使用一个“向量”即可。 - Karoly Horvath
9
@KarolyHorvath 你怎么知道这种方法在每种情况下都是可行的?如果没有标准库支持,如何部署嵌入式系统?如果需要与可能执行realloc的C接口集成呢? - Kyle Strand
7
@KarolyHorvath,你可以进行基准测试,比较使用malloc分配200 MB内存需要多长时间(几微秒),而使用std::vector则需要多长时间(约200毫秒!)。 std::vector并不是解决所有内存问题的万能解决方案。 - David Haim
2
@DavidHaim:我简直不敢相信,但我也进行了基准测试,问题似乎是向量对内存的零初始化。 int *n = new int [200 * 1024 * 1024] {}; 在MSVC上的表现大致相同。 - Oberon
5
@DavidHaim说:“reserve不会将字节清零。你可能混淆了reserveresize。” - Oberon
显示剩余7条评论
9个回答

56

只有通过malloc(或类似的家族函数,如calloc)分配的内存块可以使用realloc

这是因为跟踪空闲和已用内存区域的底层数据结构可能会非常不同。

尽管C++的new和C的malloc很可能但并不保证使用相同的底层分配器,因此在UB-land中,realloc可能适用于两者。但在实践中,这只是无谓的风险。


C++没有提供与realloc相对应的功能。

最接近的是(容器内部缓冲区的)自动重新分配,例如std::vector

C++容器的设计方式排除了使用realloc


与呈现代码不同

int* data = new int[3];
//...
int* mydata = (int*)realloc(data,6*sizeof(int));

… 做这个:

vector<int> data( 3 );
//...
data.resize( 6 );

然而,如果你绝对需要realloc的通用效率,并且必须接受new进行原始分配,那么你唯一的效率选择是使用编译器特定手段,即了解realloc在此编译器中是安全的。

否则,如果您绝对需要realloc的通用效率但不强制接受new,则可以使用mallocrealloc。然后使用智能指针可以使您获得与C++容器同样的安全性。


1
你写的代码片段是在C++中重新分配内存最惯用的方式,但如果你在这个领域,这也是一种杀死性能的方法。 - David Haim
3
如果必须接受原始分配的“new”,那么提高效率的唯一途径是使用特定于编译器的手段,例如知道该编译器中realloc 是安全的。否则,您可以使用智能指针与mallocrealloc。无论如何,请记住优化的第一(和第二)规则,即测量 - Cheers and hth. - Alf
使用智能指针确实需要使用自定义删除器来调用free()而不是delete,对吗? - Kyle Strand
1
@KyleStrand:对于标准库的智能指针,是的,您需要为内置类型定义自定义删除器。对于类类型,一个好的替代方案是重新定义类型的分配和释放函数(为了让尽可能多的初学者感到困惑,它们分别被命名为 operator new[]operator delete[],为了超越可能性而混淆事情,它们是静态的,但释放实际上像虚拟函数一样运行)。第三个选项是从头开始定义自己的智能指针,其中您可能会发现 boost::intrusive_ptr 有所帮助。 - Cheers and hth. - Alf
@Cheersandhth.-Alf:但是表现得好像它们是虚拟的?更严谨地说,它们的行为就像虚拟析构函数一样。 - Deduplicator
显示剩余2条评论

17

C++对realloc可能添加的唯一相关限制是,C++的malloc/calloc/realloc不能以::operator new的形式实现,而其free也不能以::operator delete的形式实现(根据C++14 [c.malloc]p3-4)。

这意味着你所寻找的保证在C++中不存在。然而,这也意味着你可以用malloc实现::operator new。如果你这样做了,那么理论上,::operator new的结果可以传递给realloc

实际上,你应该关注的是new的结果可能与::operator new的结果不匹配。例如,C++编译器可能会将多个new表达式组合成一个单独的::operator new调用。我记得在标准不允许时,编译器已经这样做了,而现在标准允许这样做(根据C++14 [expr.new]p10)。这意味着即使你选择这条路,你仍然不能保证将你的new指针传递给realloc会有任何有意义的效果,即使它不再是未定义的行为。

1
请为以下两个问题添加参考文献:(1) "C++的malloc/calloc/realloc不能用::operator new实现",以及(2)关于"C++编译器可能将多个new表达式组合成一个单独的::operator new调用"这一实践尚未得到标准认可。 - Cheers and hth. - Alf
@Cheersandhth.-Alf 为第一个添加了一个引用。我没有包含实际的标准文本,因为这不是一个[语言律师]问题。我没有准备好一个多个new调用的示例,可以给出我描述的结果,而一个快速简单的示例只是删除了分配的内存,并没有将分配组合成一个,它只是完全优化了分配。 - user743382

8
一般情况下不要这样做。如果您使用具有非平凡初始化的用户定义类型,在重新分配、复制和释放时,realloc不会调用对象的析构函数,也不会在复制时调用复制构造函数。这可能会导致由于对象生命周期的不正确使用而导致未定义行为(请参见)。

1 对象的生命周期是对象的运行时属性。如果一个对象属于类或聚合类型,并且它或其成员之一被除了平凡默认构造函数以外的构造函数初始化,则称该对象具有非平凡初始化。[注:用平凡的复制/移动构造函数进行初始化是非平凡初始化。——注释结束]

类型为T的对象的寿命始于:

— 获得了与类型T的大小和适当对齐的存储器,以及

— 如果对象有非平凡初始化,则其初始化完成。

类型为T的对象的生命周期结束于:

— 如果T是带有非平凡析构函数(12.4)的类类型,则析构函数调用开始,或者

— 该对象所占用的存储器被重用或释放。

稍后的部分(重点在我):

3 本国际标准中赋予对象的属性仅适用于给定对象的生命周期

因此,您真的不想在对象生命周期之外使用它。


5

这并不安全,也不够优雅。

虽然可能通过重载new/delete实现重新分配内存,但考虑到更好的方式是使用容器。


4
我不确定realloc有什么不优雅的地方。 - Kyle Strand
使用new/delete与realloc、覆盖或其他方式使其工作,不够优雅,请阅读主题。 - Non-maskable Interrupt
那么你的意思是因为它不安全,所以试图使其安全是不优雅的?从你的回答中并不清楚。请不要假设我在“阅读主题”之前就已经成功地在你的回答上留下了评论;这是毫无意义的侮辱。 - Kyle Strand

5
一般来说,不行。要使其安全,必须满足以下一些条件:
  1. 对类型进行位拷贝并放弃源必须是安全的。
  2. 析构函数必须是平凡的,或者您必须就地销毁要释放的元素。
  3. 要么构造函数是平凡的,要么您必须在原地构造新元素。
平凡类型满足上述要求。
另外还需要注意以下内容:
  1. new[]函数必须将请求传递给malloc而不进行任何更改,也不进行任何旁边的簿记。您可以通过替换全局new[]和delete[]或相应类中的new[]和delete[]来强制执行此操作。
  2. 编译器不应为了节省分配的元素数量或其他信息而要求更多的内存。
    虽然没有办法强制执行这一点,但如果类型具有平凡的析构函数,则编译器不应保存这样的信息,这是出于实现质量方面的考虑。

4
是的-如果new实际上首先调用malloc(例如,这就是VC++ new的工作方式)。

否则不行。请注意,一旦您决定重新分配内存(因为new调用了malloc),您的代码就是编译器特定的,不能在编译器之间进行移植。

(我知道这个答案可能会让很多开发人员感到不满,但我的回答基于真实事实,而不仅仅是习惯用语。)


这是否适用于 operator new[](),而不是普通的 operator new() - Alan Stokes
在VC++中,所有标准的new操作符最终都会调用malloc - David Haim
是的,但如果operator new[]的结果与调用malloc返回的值相同,我会感到惊讶,因为它存储了计数。如果不同,那么您就不能将其传递给realloc - Alan Stokes
2
存储计数的是堆条目,而计数是字节计数,而不是对象计数。因此,在分配方面,“new()”和“new []”之间没有区别,两者都调用“malloc”,该函数调用“HeapAlloc”。 - David Haim
1
只有当new[]直接返回malloc的结果而没有在数组大小前添加(对于非平凡析构函数是必需的)时,才是正确的。 - ratchet freak
显示剩余9条评论

4

这是不安全的。首先,您传递给realloc的指针必须来自于mallocreallochttp://en.cppreference.com/w/cpp/memory/c/realloc

其次,new int [3]的结果可能与分配函数的结果不同 - 可能会分配额外的空间来存储元素的计数。

(对于比int更复杂的类型,由于它不调用复制或移动构造函数,因此realloc也不安全。)


3

您可能能够(并非在所有情况下)使用,但您不应该这样做。如果您需要调整数据表的大小,则应改用std :: vector

有关如何使用它的详细信息,请参阅其他SO问题中列出的内容。


-2

这些函数主要用于C语言。

memset将内存块中的字节设置为特定值。

malloc分配一块内存。

calloc与malloc相同,唯一的区别是它将字节初始化为零。

在C++中,首选分配内存的方法是使用new。

C: int intArray = (int*) malloc(10 *sizeof(int)); C++: int intArray = new int[10];

C: int intArray = (int*) calloc(10 *sizeof(int)); C++: int intArray = new int[10];


2
我不认为这个回答解决了问题,因为它根本没有涉及重新分配的问题。 - Kyle Strand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接