何时使用malloc和/或new?

595

C++有多种分配和释放数据的方式。我知道使用malloc时应该用free来释放内存,而使用new操作符时应该与delete搭配使用。如果混合使用这两种方法(例如调用free()释放由new操作符创建的对象),那么将是一个错误。但是在实际编程中,我不清楚什么情况下应该使用malloc/free以及何时应该使用new/delete

如果您是C++专家,请告诉我在这方面遵循的任何经验法则或惯例。


43
我想提醒一下,您不能混合使用两种风格——也就是说,您不能使用new创建一个对象,然后调用free()释放它,也不能尝试删除由malloc()分配的块。这可能很显而易见,但还是要注意一下... - nsayer
46
好的回答,我要补充一点(我之前没看到的)是,new/delete会自动调用构造函数/析构函数,而malloc/free则不会。这是一个值得提及的区别。 - Bill K
3
对于现代C++,我仍在寻找使用它们的理由。 - Rahly
或者使用std:shared_ptr<T>,这样你就不必删除了。 - Vincent
1
std::unique_ptr<T> 应该是默认选择,而不是 std::shared_ptr<T>。 - avernus
20个回答

487

除非你被强制使用C语言,否则永远不要使用malloc。始终使用new

如果你需要一个大的数据块,只需执行以下操作:

char *pBuffer = new char[1024];

注意,这并不正确:

//This is incorrect - may delete only one element, may corrupt the heap, or worse...
delete pBuffer;

当删除数据数组时,您应该执行以下操作:

//This deletes all items in the array
delete[] pBuffer;
new关键字是C++执行此操作的方式,并确保该类型将调用其构造函数。相比之下,malloc完全不具备类型安全性。
我能想到唯一有益于使用malloc的方法是,如果您需要更改数据缓冲区的大小new关键字没有像realloc那样类似的方式。 realloc函数可能能够更有效地扩展内存块的大小。
值得注意的是,您不能混合使用new/freemalloc/delete
注:本问题中某些答案无效。
int* p_scalar = new int(5);  // Does not create 5 elements, but initializes to 5
int* p_array  = new int[5];  // Creates 5 elements

3
关于在应该调用delete []foo时却调用delete foo的问题,一些编译器会自动修复这个问题并不会泄漏内存,而另一些则只会删除第一个条目并泄漏内存。我在一些代码中遇到过这种情况,使用valgrind可以找到这些问题。 - KPexEA
41
如果你不使用正确的删除方法,结果是未定义的。这是错误的。即使它有时可能得到某些正确的部分或者工作正常,那也只是盲目的运气。 - Michael Burr
10
即使一些编译器可以修正您的错误,但首先犯错仍然是错误的 :) 在适当的情况下始终使用delete[]。 - korona
92
除非你被迫使用C语言,否则你永远不应该使用malloc。始终使用new。为什么?这样做有什么好处?对于我们需要构造的对象,但对于内存块而言,您明确记录了两种可能导致编码错误的方法(在new中更容易发现的()与[]之间的区别,以及在数组和标量new和delete之间不容易发现的匹配错误)。那么,使用new/delete来处理原始内存块的动机是什么? - Ben Supnik
4
如果一个人正在创建一个用于异步 API 函数的数组,使用 new[] 比使用 std::vector 更安全吗?如果使用 new[],指针变为无效的唯一方式是通过显式调用 delete,而为 std::vector 分配的内存可能会在向量被调整大小或离开作用域时失效。(请注意,在使用 new[] 时,必须考虑到可能无法调用 delete,如果异步方法仍然处于挂起状态,则可能需要通过回调来安排删除;如果需要放弃异步操作,可能需要安排回调来执行删除)。 - supercat
显示剩余19条评论

167
简短回答是:如果没有真正充分的理由,请不要在C++中使用malloc。当与C++一起使用时,malloc存在许多缺陷,而new被定义为克服这些缺陷。

new为C ++代码解决的缺陷

  1. malloc is not typesafe in any meaningful way. In C++ you are required to cast the return from void*. This potentially introduces a lot of problems:

    #include <stdlib.h>
    
    struct foo {
      double d[5];
    }; 
    
    int main() {
      foo *f1 = malloc(1); // error, no cast
      foo *f2 = static_cast<foo*>(malloc(sizeof(foo)));
      foo *f3 = static_cast<foo*>(malloc(1)); // No error, bad
    }
    
  2. It's worse than that though. If the type in question is POD (plain old data) then you can semi-sensibly use malloc to allocate memory for it, as f2 does in the first example.

    It's not so obvious though if a type is POD. The fact that it's possible for a given type to change from POD to non-POD with no resulting compiler error and potentially very hard to debug problems is a significant factor. For example if someone (possibly another programmer, during maintenance, much later on were to make a change that caused foo to no longer be POD then no obvious error would appear at compile time as you'd hope, e.g.:

    struct foo {
      double d[5];
      virtual ~foo() { }
    };
    

    would make the malloc of f2 also become bad, without any obvious diagnostics. The example here is trivial, but it's possible to accidentally introduce non-PODness much further away (e.g. in a base class, by adding a non-POD member). If you have C++11/boost you can use is_pod to check that this assumption is correct and produce an error if it's not:

    #include <type_traits>
    #include <stdlib.h>
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      return static_cast<foo*>(malloc(sizeof(foo)));
    }
    

    Although boost is unable to determine if a type is POD without C++11 or some other compiler extensions.

  3. malloc returns NULL if allocation fails. new will throw std::bad_alloc. The behaviour of later using a NULL pointer is undefined. An exception has clean semantics when it is thrown and it is thrown from the source of the error. Wrapping malloc with an appropriate test at every call seems tedious and error prone. (You only have to forget once to undo all that good work). An exception can be allowed to propagate to a level where a caller is able to sensibly process it, where as NULL is much harder to pass back meaningfully. We could extend our safe_foo_malloc function to throw an exception or exit the program or call some handler:

    #include <type_traits>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      static_assert(std::is_pod<foo>::value, "foo must be POD");
      foo *mem = static_cast<foo*>(malloc(sizeof(foo)));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return mem;
    }
    
  4. Fundamentally malloc is a C feature and new is a C++ feature. As a result malloc does not play nicely with constructors, it only looks at allocating a chunk of bytes. We could extend our safe_foo_malloc further to use placement new:

    #include <stdlib.h>
    #include <new>
    
    void my_malloc_failed_handler();
    
    foo *safe_foo_malloc() {
      void *mem = malloc(sizeof(foo));
      if (!mem) {
         my_malloc_failed_handler();
         // or throw ...
      }
      return new (mem)foo();
    }
    
  5. Our safe_foo_malloc function isn't very generic - ideally we'd want something that can handle any type, not just foo. We can achieve this with templates and variadic templates for non-default constructors:

    #include <functional>
    #include <new>
    #include <stdlib.h>
    
    void my_malloc_failed_handler();
    
    template <typename T>
    struct alloc {
      template <typename ...Args>
      static T *safe_malloc(Args&&... args) {
        void *mem = malloc(sizeof(T));
        if (!mem) {
           my_malloc_failed_handler();
           // or throw ...
        }
        return new (mem)T(std::forward(args)...);
      }
    };
    

    Now though in fixing all the issues we identified so far we've practically reinvented the default new operator. If you're going to use malloc and placement new then you might as well just use new to begin with!


35
C++让“struct”和“class”基本上意味着同一件事情,这太糟糕了;我想知道是否将“struct”保留为POD并且可能假定所有“class”类型都是非POD会有任何问题。任何在C++发明之前定义的代码类型都必须是PODs,因此我认为向后兼容性不应该是一个问题。声明非POD类型时,用“struct”而不是“class”是否有优点? - supercat
1
@supercat 有点晚了,但事实证明,让 structclass 做几乎相同的事情是一个很棒的设计决策,现在它使得一个称为 "metaclasses" (from Herb) 的新特性成为可能。 - Rakete1111
@Rakete1111:乍一看,该提案似乎预处理了一种使用以美元符号为前缀的关键字(如$class)的语言版本。然而,我不确定这与classstruct成为同义词有什么关系。 - supercat
@supercat 类型系统将会被更加分化。通过让 classstruct 的意义基本相同,您可以对它们进行任意转换($class),而不必担心将 class 变成 struct 或者反过来。 - Rakete1111
1
@Rakete1111: 如果某些操作和转换对某些类型是安全的,但对其他类型不安全,则让类型直接识别它,并让编译器拒绝不安全的操作和转换似乎比将对 PODS 适用的元类更改用于仅适用于非 PODS 的方式更好。 - supercat
显示剩余2条评论

59
C++ FQA Lite
[16.4] 为什么我应该使用new而不是值得信赖的旧malloc()?
FAQ:new/delete调用构造函数/析构函数;new是类型安全的,而malloc不是;new可以被类覆盖。
FQA:FAQ提到的new的优点并不是优点,因为构造函数、析构函数和运算符重载都是垃圾(看看当你没有垃圾收集时会发生什么?),而类型安全问题在这里真的很小(通常您必须将由malloc返回的void*强制转换为正确的指针类型以将其分配给一个类型化的指针变量,这可能很烦人,但远非“不安全”)。
哦,使用值得信赖的旧malloc使得使用同样值得信赖的旧realloc成为可能。太遗憾了,我们没有一个闪亮的新operator renew或其他东西。
尽管如此,在一个语言中坚持使用普遍风格,即使是C++,new也不足以证明偏离。特别是具有非平凡构造函数的类如果只是简单地malloc对象,将会以致命的方式表现不良。所以为什么不在整个代码中使用new呢?人们很少重载operator new,所以它可能不会太妨碍你。如果他们确实重载了new,您可以随时要求他们停止。

11
我无法认真对待这条评论,因为它明显展现了作者对C++的偏见。 C++是用于创建性能导向软件的语言,垃圾收集器只会损害它的目标。我不同意你的整个回答! - Miguel
3
@Miguel,你错过了这个笑话。 - Dan Bechard

55

在 C++ 中始终使用 new。如果您需要一个未经类型化的内存块,则可以直接使用 operator new:

void *p = operator new(size);
   ...
operator delete(p);

4
有趣,我总是在需要原始数据缓冲区时分配一个无符号字符数组。 - Greg Rogers
请注意语义应该是这样的:p_var = new type(initializer); 而不是 size。 - Brian R. Bondy
13
如果您直接调用operator new,则需要将要分配的字节数作为参数传递。 - Ferruccio
2
不太确定,我从未听说过这种语法。 - Brian R. Bondy
@Greg - 任何一种方式都可以。我只是喜欢调用operator new的事实,因为它强调了我正在进行低级别的无类型分配。 - Ferruccio
10
operator new 的相反操作是 operator delete。对于类型为 void* 的表达式调用 delete 不是一种明确定义的行为。 - CB Bailey

52

new vs malloc()

1) new是一个运算符,而malloc()是一个函数

2) new调用构造函数,而malloc()不会。

3) new返回确切的数据类型,而malloc()返回void *

4) new从不返回NULL(失败时将抛出异常),而malloc()会返回NULL。

5) 内存重新分配由new不处理,而malloc()可以处理。


13
嗨,针对第四点,可以指示new在失败时返回NULLchar* ptr = new (std::nothrow) char[323232]; 的意思是申请一个大小为323232的字符数组,如果申请失败,则返回NULL - Singh
2
  1. new根据构造函数参数创建对象,而malloc使用大小。
- Evan Moran
1
还有一个 new 函数。 - Ma Ming
1
如果你在C语言中想要进行重新分配内存,我希望你使用realloc而不是malloc,并且将指针变量初始化为NULL。另一方面,如果你想要一个可调整大小的内存块,在C++中,我会建议你使用std::vector而不是realloc...或者使用文件。 - autistic

35
为了回答你的问题,你需要知道 mallocnew之间的区别。这个区别很简单: malloc分配内存,而new 不仅分配内存,还会调用你要为之分配内存的对象的构造函数
所以,除非你被限制使用C语言,否则在处理C++对象时,永远不应该使用malloc。那样会导致程序崩溃。
同样,freedelete之间的区别也相当类似。不同之处在于,delete除了释放内存之外,还会调用你的对象的析构函数。

34

只在需要使用c-centric库和API来管理内存时,使用mallocfree。在你可以控制的所有情况下,使用newdelete(以及[]变体)。


11
请注意,写得好的 C 库会在内部隐藏 malloc 和 free,这是 C 程序员应该遵循的方式。 - Dacav
@dmckee 你有使用malloc和free函数的C++与c-centric库的示例吗? - milesma
3
如果一个C函数需要在返回后仍需继续使用一个对象的指针,并且调用者没有办法知道何时这个对象不再需要,那么函数要求该指针必须由malloc创建是完全合理的。同样地,如果像strdup这样的函数需要创建并返回一个对象给调用者,那么要求调用者在不再需要该对象时调用free也是完全合理的。这些函数如何避免向调用者公开它们对malloc/free的使用呢? - supercat
@supercat,将指针传递给C函数接受对象本质上是有问题的,因为C根本不知道对象。一般来说,我认为在C中,最好的方法是在分配/释放周围使用语义包装器。如果C库要求调用者预先分配和/或释放内存,则仍然可以接受,但灵活性较差。如果C函数正在执行此操作并声称拥有分配的内存,则您需要使用malloc隐式地分配它。 - Dacav
1
从C++标准中,章节名为The C++ object model,我们可以看到对于对象的定义:*"对象是存储区域。"* 在C标准中也有类似的定义;在C和C++中,char c;这个变量都表示一个对象。不同之处在于,C++中的一些(但不是全部)对象也是多态的(毕竟C++是面向对象的)。不要误以为只有面向对象的代码才能使用对象。 - autistic
显示剩余11条评论

16
mallocnew之间有一个很大的区别。 malloc分配内存。这对于C语言来说是可以的,因为在C语言中,一块内存就是一个对象。
在C++中,如果你不处理POD类型(与C类型类似),你必须在内存位置上调用构造函数才能实际拥有一个对象。非POD类型在C++中非常常见,因为许多C++特性会自动使对象成为非POD类型。 new在分配内存的同时创建一个对象并将其放置在该内存位置上。对于非POD类型,这意味着调用构造函数。
如果你做了这样的事情:
non_pod_type* p = (non_pod_type*) malloc(sizeof *p);

您获得的指针无法被解引用,因为它没有指向任何对象。在使用它之前,您需要调用构造函数(可以使用放置 new 实现)。

另一方面,如果您执行以下操作:

non_pod_type* p = new non_pod_type();

你得到的指针始终有效,因为new创建了一个对象。
即使对于POD类型,这两者之间仍存在显着差异:
pod_type* p = (pod_type*) malloc(sizeof *p);
std::cout << p->foo;

这段代码会打印出一个未指定的值,因为malloc创建的POD对象没有初始化。

使用new,您可以指定要调用的构造函数,从而获得一个明确定义的值。

pod_type* p = new pod_type();
std::cout << p->foo; // prints 0

如果你真的想要,可以使用new获取未初始化的POD对象。有关详细信息,请参见此其他答案
另一个差异是失败时的行为。当无法分配内存时,malloc返回空指针,而new会抛出异常。
前者需要您在使用每个返回的指针之前进行测试,而后者总是会生成有效指针。
因此,在C++代码中应该使用new,而不是malloc。但即使这样,也不应该“公开地”使用new,因为它会获取需要稍后释放的资源。当使用new时,应立即将其结果传递给资源管理类:
std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak

16

只有在对象的生命周期应与创建它的范围不同(缩小或扩大范围都适用)且存储到值中不起作用的特定原因时,才需要动态分配。

例如:

 std::vector<int> *createVector(); // Bad
 std::vector<int> createVector();  // Good

 auto v = new std::vector<int>(); // Bad
 auto result = calculate(/*optional output = */ v);
 auto v = std::vector<int>(); // Good
 auto result = calculate(/*optional output = */ &v);
自C++11起,我们有std::unique_ptr来处理已分配的内存,它包含了已分配内存的所有权。当需要共享所有权时,创建std::shared_ptr(在一个好的程序中,你会需要这个比你预期的少)。创建实例变得非常简单:
auto instance = std::make_unique<Class>(/*args*/); // C++14
auto instance = std::unique_ptr<Class>(new Class(/*args*/)); // C++11
auto instance = std::make_unique<Class[]>(42); // C++14
auto instance = std::unique_ptr<Class[]>(new Class[](42)); // C++11

C++17还增加了std::optional,可以避免您需要进行内存分配。

auto optInstance = std::optional<Class>{};
if (condition)
    optInstance = Class{};

'instance'一旦超出作用域范围,内存将被清除。转移所有权也很容易:

 auto vector = std::vector<std::unique_ptr<Interface>>{};
 auto instance = std::make_unique<Class>();
 vector.push_back(std::move(instance)); // std::move -> transfer (most of the time)

从C++11开始,你几乎不再需要使用new了。大多数情况下,你可以使用std::make_unique,直到你遇到一个使用裸指针来传递所有权的API。

 auto instance = std::make_unique<Class>();
 legacyFunction(instance.release()); // Ownership being transferred

 auto instance = std::unique_ptr<Class>{legacyFunction()}; // Ownership being captured in unique_ptr

在C++98/03中,您需要进行手动内存管理。如果您处于这种情况,请尝试升级到更高版本的标准。如果您陷入困境:

在 C++98/03 中需要手动内存管理。如遇此情况,建议升级至较新版本的标准,若无法升级请:

 auto instance = new Class(); // Allocate memory
 delete instance;             // Deallocate
 auto instances = new Class[42](); // Allocate memory
 delete[] instances;               // Deallocate

确保你正确地跟踪所有权,以避免任何内存泄漏!移动语义尚不起作用。

那么,在C++中什么时候需要使用malloc?唯一有效的原因是通过放置new来分配内存并稍后初始化它。

 auto instanceBlob = std::malloc(sizeof(Class)); // Allocate memory
 auto instance = new(instanceBlob)Class{}; // Initialize via constructor
 instance.~Class(); // Destroy via destructor
 std::free(instanceBlob); // Deallocate the memory
尽管如上是有效的,但也可以通过新操作符来完成。std::vector是一个很好的例子。 最后,我们仍然有一只大象在房间里:C语言。如果你必须使用在C++代码中分配内存并在C代码中释放内存(或者反过来)的C库进行工作,那么你就被迫使用malloc/free。 如果你处于这种情况下,请忘记虚函数、成员函数、类等等。只允许使用带有POD的结构体。 一些规则的例外情况: -您正在编写具有高级数据结构的标准库,其中malloc是适当的; -您必须分配大量内存(内存复制10GB文件?); -您有工具阻止您使用某些构造; -您需要存储不完整类型。

1
肯定是+1! new/delete和malloc/free是我们的祖先使用的。这些不是现代C++中执行动态分配的推荐方式。堆栈上的对象和容器、unique_ptr和shared_ptr是处理对象的“正确”方式。我有一些示例项目,其中包含数十万行代码,没有任何mallocnew调用。 - Russell Trahan

7

在一些方面,newmalloc 更好:

  1. new 通过调用对象的构造函数来构造该对象。
  2. new 不需要分配的内存进行类型转换。
  3. 它不需要分配一定数量的内存,而是需要构造一定数量的对象。

因此,如果您使用 malloc,则需要显式执行上述操作,这并不总是实用的。此外,new 可以重载,但是 malloc 不能。


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