‘malloc’和’new’如何运作? 它们有何不同(实施明智)?

我知道它们在语法上是如何不同的,并且C ++使用new,而C使用malloc。 但他们是如何运作的,在一个高级别的解释中呢?

看看new / delete和malloc / free有什么区别?

我只想指导你这个答案: new / delete和malloc / free有什么区别? 。 马丁提供了很好的概述。 快速了解它们的工作原理 (无需考虑如何将它们作为成员函数重载):

新表达和分配

  1. 该代码包含一个提供type-id的new-expression。
  2. 编译器将查看类型是否使用分配函数重载operator new。
  3. 如果它找到运算符新分配函数的重载,则使用赋予new和sizeof(TypeId)作为其第一个参数的参数调用该函数:

样品:

new (a, b, c) TypeId; // the function called by the compiler has to have the following signature: operator new(std::size_t size, TypeOfA a, TypeOfB b, TypeOf C c); 
  1. 如果operator new无法分配存储空间,它可以调用new_handler ,并希望它能够new_handler 。 如果仍然没有足够的位置,new必须抛出std::bad_alloc或从中派生。 一个具有throw() (无抛出保证)的分配器,在这种情况下它应该返回一个空指针。
  2. C ++运行时环境将创建由分配函数返回的内存中由type-id指定的类型的对象。

给出了一些特殊名称的特殊分配函数:

  • no-throw新的。 这需要一个nothrow_t作为第二个参数。 如下所示的表单的新表达式将调用仅使用std :: size_t和nothrow_t的分配函数:

例:

 new (std::nothrow) TypeId; 
  • placement new 。 这将void *指针作为第一个参数,而不是返回新分配的内存地址,它返回该参数。 它用于在给定地址创建对象。 标准容器使用它来预分配空间,但稍后只在需要时创建对象。

码:

 // the following function is defined implicitly in the standard library void * operator(std::size_t size, void * ptr) throw() { return ptr; } 

如果分配函数返回存储,并且运行时创建的对象的构造函数抛出,则会自动调用operator delete。 如果使用了一种新的forms,它需要额外的参数,比如

 new (a, b, c) TypeId; 

然后调用那些参数的运算符delete。 只有在删除完成时才调用该运算符删除版本,因为对象的构造函数确实抛出了。 如果你自己调用delete,那么编译器将使用普通的operator delete函数,只取一个void*指针:

 int * a = new int; => void * operator new(std::size_t size) throw(std::bad_alloc); delete a; => void operator delete(void * ptr) throw(); TypeWhosCtorThrows * a = new ("argument") TypeWhosCtorThrows; => void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc); => void operator delete(void * ptr, char const* arg1) throw(); TypeWhosCtorDoesntThrow * a = new ("argument") TypeWhosCtorDoesntThrow; => void * operator new(std::size_t size, char const* arg1) throw(std::bad_alloc); delete a; => void operator delete(void * ptr) throw(); 

new-expression和数组

如果你这样做

 new (possible_arguments) TypeId[N]; 

编译器使用operator new[]函数而不是plain operator new 。 操作符可以传递第一个不完全是sizeof(TypeId)*N参数sizeof(TypeId)*N :编译器可以添加一些空间来存储创建的对象数(为了能够调用析构函数而需要)。 标准就是这样说的:

  • new T[5]导致调用operator new[](sizeof(T)*5+x) ,和
  • new(2,f) T[5]导致运算符new[](sizeof(T)*5+y,2,f)的调用new[](sizeof(T)*5+y,2,f)

malloc不同之处在于以下内容:

  • 它通过调用operator new在分配的内存中构造一个值。 可以通过重载此运算符来调整此行为,适用于所有类型,或仅适用于您的类。
  • 如果不能分配内存,则调用处理函数。 如果您事先已经注册了这样的处理函数,这使您有机会即时释放所需的内存。
  • 如果这没有帮助(例如因为你没有注册任何函数),它会抛出exception。

总而言之, new是高度可定制的,并且除了内存分配之外还进行初始化工作。 这是两个很大的不同。

尽管malloc / freenew / delete具有不同的行为,但它们都在低级别执行相同的操作:管理动态分配的内存。 我假设这是你真正要问的。 在我的系统上, new实际上在内部调用malloc来执行它的分配,所以我只谈谈malloc

mallocfree的实际实现可能有很大差异,因为有很多方法可以实现内存分配。 一些方法获得更好的性能,一些方法浪费更少的内存,另一些方法更好的调试 垃圾收集语言也可能有完全不同的分配方式,但您的问题是关于C / C ++。

通常,块是从堆中分配的,程序的地址空间中有大面积的内存。 库为您管理堆,通常使用sbrkmmap等系统调用。 从堆分配块的一种方法是维护存储块大小和位置的空闲和分配块的列表。 最初,列表可能包含整个堆的一个大块。 当请求新块时,分配器将从列表中选择一个空闲块。 如果块太大,它可以分成两个块(一个是所请求的大小,另一个是剩下的大小)。 当释放分配的块时,它可以与相邻的空闲块合并,因为具有一个大的空闲块比几个小的空闲块更有用。 实际的块列表可以存储为单独的数据结构或嵌入到堆中。

有很多变化。 您可能希望保留单独的空闲和已分配块列表。 如果对于常见大小的块具有堆的单独区域,或者对于这些大小的单独列表,则可能会获得更好的性能。 例如,当您分配一个16字节的块时,分配器可能有一个特殊的16字节块列表,因此分配可以是O(1)。 仅处理2的幂的块大小(其他任何东西被四舍五入)也可能是有利的。 例如, Buddy分配器以这种方式工作。

“new”比malloc做得更多。 malloc只是分配内存 – 它甚至不会为你归零。 新的初始化对象,调用构造函数等。我怀疑在大多数实现中,new只不过是基于malloc的基本类型的瘦包装。

在C中:malloc分配一个你在参数中提供的大小的内存块,并返回一个指向这个内存的指针。

内存在堆上声明,因此请确保在完成后释放它。