【问题标题】:Is it safe to realloc memory allocated with new?重新分配新分配的内存是否安全?
【发布时间】:2021-12-30 23:34:50
【问题描述】:

根据here 所写的内容,newfree store 中分配,而malloc 使用heap,这两个术语通常意味着相同的东西。 p>

根据here 所写的内容,realloc 可能会将内存块移动到新位置。如果free store和heap是两个不同的内存空间,那是不是意味着什么问题呢?

特别想知道使用是否安全

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

如果没有,有没有其他方法可以安全地使用new 分配realloc 内存?我可以分配新区域和memcpy 的内容,但据我了解realloc 可能会尽可能使用相同的区域。

【问题讨论】:

  • 只需使用vector
  • @KarolyHorvath 你怎么知道这在任何情况下都是可行的方法?如果在没有标准库支持的情况下部署嵌入式系统呢?与可能执行重新分配的 C 接口集成怎么样?
  • @KarolyHorvath 欢迎您对malloc(几微秒)与std::vector(~200 毫秒!)分配 200 MB 内存所需的时间进行基准测试。 std::vector 并不是所有内存问题的灵丹妙药
  • @DavidHaim:我不敢相信,但我也对其进行了基准测试,问题似乎是向量对内存的零初始化。 int* n = new int[200 * 1024 * 1024]{}; 在 MSVC 上的性能大致相同。
  • @DavidHaim: 否 reserve 不会将字节归零。您可能会将其与 resize 混淆。

标签: c++ memory-management realloc


【解决方案1】:

您只能通过malloc(或家庭,如calloc)分配的realloc

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

可能,但绝不保证 C++ new 和 C malloc 使用相同的底层分配器,在这种情况下 realloc 可以同时工作。但正式地那是在UB-land。在实践中,这只是不必要的冒险。


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++ 容器相同的安全性。

【讨论】:

  • 你写的 sn-p 是在 C++ 中重新分配内存的最惯用的方法,但如果你在这个领域,它肯定会扼杀你的表现。
  • @KyleStrand:如果您必须接受new 进行原始分配,那么您唯一的效率办法就是使用编译器特定的方法。例如。知道realloc 使用此编译器是安全的。否则,您可以将智能指针与mallocrealloc 一起使用。无论如何,请记住第一条(和第二条)优化规则,即 MEASURE
  • 使用智能指针确实需要使用自定义删除器来调用 free() 而不是 delete,对吧?
  • @KyleStrand:使用标准库的智能指针,是的,您需要一个用于内置类型的自定义删除器。对于类类型,一个不错的选择是重新定义类型的分配和释放函数(为了混淆尽可能多的初学者,它们分别命名为 operator new[]operator delete[],只是为了混淆它们可能之外的东西静态但释放有效地充当虚拟)。第三种选择是从头开始定义您自己的智能指针,您可能会发现 boost::intrusive_ptr 对此很有帮助。
  • 这给实现std::vector的标准库作者带来了一个有趣的困境:既然realloc很方便,他们应该使用malloc还是更惯用的new
【解决方案2】:

不安全,也不优雅。

可能会覆盖 new/delete 以支持重新分配,但您不妨考虑使用容器。

【讨论】:

  • 我不确定 realloc 有什么不好的地方。
  • 使用 new/delete 和 realloc、通过覆盖或其他方式使其工作,不优雅,请阅读主题。
  • 所以你的意思是因为它不安全,试图使它安全是不雅的?从您的回答中不清楚。并且不要以为我已经设法在没有“阅读主题”的情况下对您的答案发表评论;这是毫无意义的侮辱。
【解决方案3】:

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

否则没有。请注意,一旦您决定重新分配内存(因为 new 称为 malloc),您的代码是特定于编译器的,不再可在编译器之间移植。

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

【讨论】:

  • 这里使用的operator new[]() 是不是这样,而不是普通的operator new()
  • 在 VC++ 上,所有标准的new 运算符最终都调用malloc
  • 是的,但如果operator new[] 的结果与调用malloc 返回的值相同,我会感到惊讶,因为存储了计数。如果不是,则不能将其传递给realloc
  • 存储计数的东西是堆入口,计数是字节数,不是对象数。所以分配明智,new()new[] 之间没有区别,两者都调用malloc 调用HeapAlloc
  • 只有当new[] 直接返回malloc 的结果而不预先添加数组的大小(这是一个重要的析构函数所需要的)时,这才是正确的
【解决方案4】:

一般来说,不要那样做。如果您使用具有非平凡初始化的用户定义类型,在重新分配复制释放的情况下,realloc 不会调用 对象的析构函数 .复制时也不会调用复制构造函数。由于对象生命周期的不正确使用,这可能会导致未定义的行为(请参阅C++ 标准§3.8 对象生命周期,[basic.life])。

1 对象的生命周期是对象的运行时属性。如果一个对象是一个类或聚合类型,并且它或它的一个成员由一个普通默认构造函数以外的构造函数初始化,则称该对象具有非普通初始化。 [注意:通过简单的复制/移动构造函数进行初始化是非平凡的初始化。 ——尾注]

类型 T 的对象的生命周期开始于:

— 获得类型 T 具有正确对齐和大小的存储,并且

——如果对象有非平凡的初始化,它的初始化就完成了。

类型 T 的对象的生命周期在以下时间结束:

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

——对象占用的存储空间被重用或释放。

后来(强调我的):

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

所以,你真的不想在生命周期之外使用一个对象

【讨论】:

    【解决方案5】:

    那不安全。首先,您传递给realloc 的指针必须是从mallocrealloc 获得的:http://en.cppreference.com/w/cpp/memory/c/realloc

    其次,new int [3] 的结果不必与分配函数的结果相同——可以分配额外的空间来存储元素的数量。

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

    【讨论】:

      【解决方案6】:

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

      如何使用它的详细信息在另一个SO question中列出。

      【讨论】:

        【解决方案7】:

        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 调用。这是编译器在标准不允许时已经做的事情,IIRC,现在标准确实允许它(根据 C++14 [expr.new]p10)。这意味着即使你走这条路,你仍然不能保证将你的 new 指针传递给 realloc 会做任何有意义的事情,即使它不再是未定义的行为。

        【讨论】:

        • 请添加对 (1) “C++ 的 malloc/calloc/realloc 不得以 ::operator new 的形式实现”的引用,以及 (2) 关于尚未得到认可的实践中的引用标准,“C++ 编译器可以例如组合多个新表达式以使用单个 ::operator new 调用”。
        • @Cheersandhth.-Alf 为第一个添加了参考。没有包含实际的标准文本,因为这不是 [语言律师] 问题。我没有准备好用于多个new 调用的示例来给出我所描述的结果,并且一个快速简单的示例只是删除分配的内存不会将分配合并为一个,它只是优化了分配完全。
        【解决方案8】:

        这些函数主要用在C语言中。

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

        malloc 分配一块内存。

        calloc,和 malloc 一样。唯一的区别是它将字节初始化为零。

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

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

        C:intintArray = (int*) calloc(10 *sizeof(int)); C++: int intArray = new int10;

        【讨论】:

        • 我认为这不能回答问题,因为它根本没有解决重新分配问题。
        【解决方案9】:

        一般来说不会。

        为了确保安全,必须遵守许多事项:

        1. 按位复制类型并放弃源必须是安全的。
        2. 析构函数必须是微不足道的,或者您必须就地销毁要释放的元素。
        3. 构造函数是微不足道的,或者您必须就地构造新元素。

        平凡类型满足上述要求。

        另外:

        1. new[] 函数必须将请求传递给malloc,不做任何更改,也不要做任何簿记。您可以通过替换全局 new[] 和 delete[] 或相应类中的来强制执行此操作。
        2. 编译器不得要求更多内存来保存分配的元素数量或其他任何事情。
          没有办法强制这样做,但如果类型具有简单的析构函数作为实现质量的问题,编译器不应保存此类信息。

        【讨论】:

          猜你喜欢
          • 2018-05-07
          • 1970-01-01
          • 2012-05-27
          • 2020-04-24
          • 1970-01-01
          • 2013-12-17
          • 1970-01-01
          相关资源
          最近更新 更多