'malloc'和'new'是如何工作的?它们在实现上有什么不同?

9

1
我不认为这是一个重复的问题。我认为Joel在问new/malloc如何分配内存,以及它们的实现方式是否有区别。这与它们对用户的行为方式不同。 - Jay Conrod
@Jay:请看Joel对@litb回答的评论。 - Robert Gamble
5个回答

10
我只是想引导您阅读这篇答案:new/delete和malloc/free有什么区别?。Martin提供了一个很好的概述。简要概述它们的工作方式(不涉及如何将它们重载为成员函数):

new表达式和分配

  1. 代码包含提供type-id的new表达式。
  2. 编译器将查找类型是否使用分配函数重载了operator new。
  3. 如果它发现operator 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. 如果运算符new在分配存储空间时失败,它可以调用new_handler来尝试释放一些空间。如果仍然没有足够的空间,则new必须抛出std::bad_alloc或其派生类。对于具有throw()(无抛出保证)的分配器,在这种情况下它应该返回一个空指针。
  2. C++运行环境将在分配函数返回的内存中创建一个与type-id指定类型相对应的对象。

有几个特殊的分配函数都有专门的名称:

  • no-throw new。它以nothrow_t作为第二个参数。以下形式的new-expression将调用只采用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。如果使用了带有其他参数的new形式,例如:

new (a, b, c) TypeId;

接着调用带有这些参数的operator delete。只有在对象的构造函数抛出异常时才会调用该operator delete版本进行删除。如果您自己调用delete,则编译器将使用仅接受void*指针的普通operator delete函数:

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[]函数而不是普通的operator new。该运算符可以传递一个第一个参数,不一定是sizeof(TypeId)*N:编译器可以添加一些空间来存储创建的对象数(必须能够调用析构函数)。标准如下所述:
  • new T[5]导致调用运算符new[](sizeof(T)*5+x)
  • new(2,f) T[5]导致调用运算符new[](sizeof(T)*5+y,2,f)

也许我只是不知道如何正确搜索... - Joel
我有一个问题。如果我有以下代码(忽略虚表等可能的错误 - 这只是一个例子): MyClass *p = malloc(sizeof(MyClass)); MyClass *q = new (p) MyClass(); 那么 p == q 总是成立吗? q 会小于 p 吗?另外,我该如何删除它?我需要手动使用 free(),对吗?谢谢!(顺便加一分) - strager
谢谢,伙计。是的,q == p,因为那个放置 new 只会从其分配函数中再次返回 p。实际上,vtable 没有任何问题,您可以创建任何类型。例如,boost::variant 就是这样做的,以便为多种类型使用一个缓冲区。 - Johannes Schaub - litb
你可以手动调用构造函数q->~MyClass()来删除对象,然后调用free(p)释放缓冲区。希望对你有所帮助,祝好运 :) - Johannes Schaub - litb
@litb,非常感谢。这确实有助于我理解C++内部,尤其是我来自C/asm背景。=] - strager

2
< p > newmalloc 的不同之处在于:

  • 它通过调用 operator new 在分配的内存中构造一个值。这种行为可以通过重载此运算符来适应所有类型或仅适应您的类。
  • 如果无法分配内存,它将调用处理程序函数。这使您有机会在注册此类处理程序函数之前即时释放所需内存。
  • 如果这些都无法解决问题(例如,因为您未注册任何函数),它将引发异常。

总而言之,new 高度可定制,并且除了内存分配外还执行初始化工作。这是两个重要的区别。


1

虽然malloc/freenew/delete的行为不同,但从低层次上看,它们都是管理动态分配的内存。我猜这才是你真正想问的。在我的系统中,new实际上内部调用了malloc来执行其分配,所以我只会谈论malloc

mallocfree的实际实现可以有很多变化,因为有许多实现内存分配的方法。有些方法可以获得更好的性能,一些浪费较少的内存,其他方法则更适合调试。垃圾回收语言可能也有完全不同的分配方式,但你的问题涉及到C/C++。

一般来说,块是从堆中分配的,堆是程序地址空间中的一个大内存区域。库会为您管理堆,通常使用像 sbrkmmap 这样的系统调用。从堆中分配块的一种方法是维护一个自由块和已分配块的列表,该列表存储块大小和位置。最初,列表可能包含整个堆的一个大块。当请求新块时,分配器将从列表中选择一个自由块。如果块太大,则可以将其拆分为两个块(一个请求的大小,另一个是剩余大小)。当释放已分配块时,它可以与相邻的自由块合并,因为拥有一个大的自由块比拥有几个小的自由块更有用。块的实际列表可以存储为单独的数据结构或嵌入到堆中。

有很多变化。您可能希望保留免费和分配块的单独列表。如果您为常见大小的块分别拥有堆的不同区域或分别拥有这些大小的列表,则可能会获得更好的性能。例如,当您分配16字节块时,分配器可能具有特殊的16字节块列表,因此分配可以是O(1)。仅处理2的幂次方块大小也可能是有利的(其他任何内容都会四舍五入)。例如,Buddy allocator 就是这样工作的。


0

"new" 做的事情比 malloc 多得多。malloc 只是分配内存 - 它甚至不会为您清零。new 初始化对象,调用构造函数等。我认为在大多数实现中,对于基本类型,new 只是一个薄包装器,它背后还是使用了 malloc。


0
在C语言中: malloc函数会分配一块指定大小的内存空间,并返回该内存空间的指针。
这块内存空间是在堆上声明的,所以在使用完毕后一定要记得释放它。

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