在流行的实现中,C和c++的动态内存分配不同吗?

Does dynamic memory allocation differ in C and C++ in popular implementations?

本文关键字:分配 内存 动态 实现 流行 c++      更新时间:2023-10-16

就各自的语言标准而言,C只通过malloc()家族提供动态内存分配,而在c++中,最常见的分配形式是由::operator new()执行的。C风格的malloc在c++中也可用,并且许多"婴儿的第一个分配器"示例使用它作为其核心分配函数,但我很好奇当代编译器如何实现实际的生产操作符-new。

它只是一个薄薄的包装围绕malloc(),或者它是实现根本不同的考虑到相当不同的内存分配行为的一个典型的c++程序相比,一个典型的C程序?

[Edit:我认为主要的区别通常描述如下:C程序有更少、更大、寿命较长的分配,而c++程序有许多、更小、寿命较短的分配。如果这是错误的,请随意插话,但听起来,考虑到这一点会使人受益。

对于像GCC这样的编译器,很容易只有一个核心分配实现,并将其用于所有相关语言,所以我想知道在每种语言中是否存在试图优化结果分配性能的细节差异。


更新:感谢所有伟大的答案!在GCC中,这个问题似乎完全由ptmalloc解决了,而且MSVC也在核心使用malloc。有人知道MSVC-malloc是如何实现的吗?

下面是g++ 4.6.1使用的实现:

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)
{
  void *p;
  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;
  p = (void *) malloc (sz);
  while (p == 0)
    {
      new_handler handler = __new_handler;
      if (! handler)
#ifdef __EXCEPTIONS
        throw bad_alloc();
#else
        std::abort();
#endif
      handler ();
      p = (void *) malloc (sz);
    }
  return p;
}

在g++源代码发行版的libstdc++-v3/libsupc++/new_op.cc中找到。

可以看到,它是malloc的一个相当薄的包装。

edit在许多系统上,可以通过调用mallopt或设置环境变量来微调malloc的行为。这里有一篇文章讨论了Linux上可用的一些特性。

根据维基百科,glibc 2.3+版本使用了一个修改版本的分配器,称为ptmalloc,它本身是由Doug Lea设计的dlmalloc的衍生物。有趣的是,在一篇关于dlmalloc的文章中,Doug Lea给出了以下观点(强调我的观点):

在写了一些c++之后,我写了第一个版本的分配器几乎完全依赖于分配动态内存的程序。我发现他们跑得更慢,而且/或者跑得更多内存消耗比我预期的要多。这是由于我正在运行的系统上的内存分配器的特征on(主要是当时的SunOs和BSD版本)。应对首先,我用c++写了一些特殊用途的分配器,通常通过重载不同类的操作符new。一些这些都是在一篇关于c++分配技术的论文中描述的改编自1989年c++ Report文章《一些存储分配》容器类的技术。

然而,我很快意识到,为每个对象构建一个特殊的分配器动态分配和大量使用的新类是在构建各种通用编程时,这不是一个好策略支持我当时正在写的课程。(从1986年到1991年,我是lib++ (GNU c++库)的主要作者。)一个更广泛的需要解决方案——为编写一个足够好的分配器在正常的c++和C加载下,这样程序员就不会被诱惑编写特殊用途的分配器,除非在非常特殊的条件。

本文介绍了一些主要设计目标的描述,这个分配器的算法和实现注意事项。

在大多数实现中,operator new()只是调用malloc()。事实上,就连《标准报》也建议将其作为默认策略。当然,您可以实现自己的operator new,通常是为了一个类,如果你想要更好的性能,但默认情况下通常只是调用malloc()

glibc new操作符是对malloc的一个薄包装。glibc malloc对不同大小的分配请求使用不同的策略。您可以在这里看到实现,或者至少看到注释。

下面是malloc.c中注释的摘录:

/*
47   This is not the fastest, most space-conserving, most portable, or
48   most tunable malloc ever written. However it is among the fastest
49   while also being among the most space-conserving, portable and tunable.
50   Consistent balance across these factors results in a good general-purpose
51   allocator for malloc-intensive programs.
52 
53   The main properties of the algorithms are:
54   * For large (>= 512 bytes) requests, it is a pure best-fit allocator,
55     with ties normally decided via FIFO (i.e. least recently used).
56   * For small (<= 64 bytes by default) requests, it is a caching
57     allocator, that maintains pools of quickly recycled chunks.
58   * In between, and for combinations of large and small requests, it does
59     the best it can trying to meet both goals at once.
60   * For very large requests (>= 128KB by default), it relies on system
61     memory mapping facilities, if supported.
*/

在Visual c++中,进入new表达式导致我在new.cpp中得到以下代码片段:

#include <cstdlib>
#include <new>
_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }
        return (p);
        }

所以vc++的new也封装了malloc()调用

这不是性能的问题:pA = new ApA = (A*)malloc(sizeof(A));有不同的副作用

在第二个例子中,没有调用A的构造函数。要达到同样的效果,你应该这样做

pA = (A*)malloc(sizeof(A));
new(pA)A();

其中new为"placement new"…

void* operator new(size_t sz, void* place) 
{ return place; }