我在什么情况下使用malloc和/或new?


479

我看到在C ++中有多种分配和释放数据的方法,而且我了解,当您调用malloc时应调用,free并且在使用new运算符时应与之配对,delete将两者混合使用是错误的(例如,调用free()已创建的内容)与new操作员联系),但我不清楚在实际程序中何时应使用malloc/ free以及何时应使用new/ delete

如果您是C ++专家,请让我知道您在这方面遵循的经验法则或惯例。


33
我只是想提醒您,您不能混合使用两种样式-也就是说,您不能使用new来创建对象,然后对其调用free(),也不能尝试删除由malloc()分配的块。大概可以这么说,但是……
nsayer

32
好的答案,我所要添加的(我尚未看到)是new / delete为您调用构造函数/析构函数,而malloc / free则没有。只是一个差异值得一提。
Bill K

对于现代C ++,我仍在尝试找到使用两者之一的原因。
拉赫利

或都不使用,并使用std:shared_ptr <T>。然后,您根本不必删除。
文森特

Answers:


387

除非您被迫使用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 ++的方式,这将确保你的类型都会有所谓的构造。该new关键字也更加类型安全,而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

2
关于在应该调用delete [] foo时调用delete foo的问题,某些编译器会自动为您解决此问题,而不泄漏,而其他编译器只会删除第一个条目并泄漏。我在一些代码中有其中一些,valgrind会为您找到它们。
KPexEA

30
如果未使用正确的删除,则结果不确定。不对 它可能使事情变得正确或起作用的事实有时只是一时的运气。
Michael Burr

8
@KPexEA:即使某些编译器可能会纠正您的错误,但首先使它们出错仍然是错误的:)始终在适当的地方使用delete []。
科罗纳

62
“除非被迫使用C,否则永远不要使用malloc。请始终使用new。” 为什么?这是什么胜利?对于需要构造的对象,但对于内存块,您清楚地记录了两种编码错误的方法(在new中更容易捕获()或[],在不匹配的数组中更容易捕获到vs和new和delete的缩放器)。对原始内存块使用new / delete的动机是什么?
Ben Supnik

3
@DeadMG:如果要创建一个供异步API函数使用的数组,会new[]比这更安全std::vector吗?如果使用new[],则指针变为无效的唯一方法是通过explicit delete,而std::vector当向量调整大小或离开作用域时,为分配的内存可能无效。(请注意,使用async时,必须考虑async方法仍处于未决状态new[]而无法调用的可能性delete;如果可能有必要放弃async操作,则可能必须安排通过回调进行删除) 。
2013年

144

简短的答案是:不要在没有malloc充分理由的情况下使用C ++。malloc与C ++一起使用时,存在许多缺陷,new可以克服这些缺陷。

新的C ++代码修复的缺陷

  1. malloc以任何有意义的方式都是类型安全的。在C ++中,您需要将返回的值强制转换为void*。这可能会带来很多问题:

    #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. 比这更糟。如果有问题的类型是POD(普通旧数据),那么您可以mallocf2第一个示例中那样半明智地使用它来为其分配内存。

    如果类型是POD,则不是很明显。一个重要的因素是,给定类型可能从POD更改为非POD,而不会导致编译器错误,并且可能很难调试问题。例如,如果某个人(可能是另一个程序员,在维护过程中,后来又进行了更改而导致foo不再是POD),那么在编译时就不会出现您希望的明显错误,例如:

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

    没有任何明显的诊断方法mallocf2也会使of 变坏。这里的示例很简单,但是有可能在更远的地方意外地引入了非POD(例如,在基类中,通过添加非POD成员)。如果您使用的是C ++ 11 / boost,则可以is_pod用来检查该假设是否正确,如果不正确,则会产生错误:

    #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)));
    }

    尽管boost 无法确定没有C ++ 11或其他一些编译器扩展的类型是否为POD

  3. mallocNULL如果分配失败,则返回。new会抛出std::bad_alloc。以后使用NULL指针的行为是不确定的。抛出异常并从错误源抛出异常时,它具有清晰的语义。malloc在每次调用时都进行适当的测试包装似乎很乏味且容易出错。(您只需忘记一次就可以撤消所有的出色工作)。可以允许将异常传播到调用者能够明智地对其进行处理的程度,而在NULL这种情况下很难将其有意义地传递回来。我们可以扩展我们的safe_foo_malloc功能以引发异常或退出程序或调用某些处理程序:

    #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. 从根本上讲malloc是C功能,new也是C ++功能。结果malloc,它与构造函数不能很好地配合,它只会分配一块字节。我们可以safe_foo_malloc进一步扩展到使用展示位置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. 我们的safe_foo_malloc函数不是很通用-理想情况下,我们希望可以处理任何类型的东西,而不仅仅是foo。我们可以使用非默认构造函数的模板和可变参数模板来实现此目的:

    #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)...);
      }
    };

    现在,尽管解决了我们迄今为止确定的所有问题,我们实际上已经重新发明了默认new运算符。如果要使用malloc和放置,new那么最好还是先使用new


27
用C ++编写的代码太糟糕了struct,它的class意思基本相同。我想知道struct为POD保留是否会出现任何问题,并且可能将所有class类型都假定为非POD。在C ++发明之前,由代码定义的任何类型都必然是POD,因此我不认为向后兼容性会成为一个问题。将非POD类型声明为struct而不有优势class
超级猫

1
@supercat有点晚了,但事实证明,做出structclass做几乎相同的事情是一个奇妙的设计决策,现在可以启用名为“ metaclasses”(来自Herb)的简洁功能。
Rakete1111 '18 -10-13

@ Rakete1111:乍一看,该提议看起来像它预处理了使用美元前缀的关键字(例如)的语言版本$class。我不知道有做什么classstruct不过是同义词。
超级猫

@supercat类型系统将被进一步分叉。由于具有classstruct平均有效同样的事情,你可以做他们(任意变换$class)而不进行堪忧class一个struct,反之亦然。
Rakete1111 '18 -10-13

@ Rakete1111:如果某些类型的操作和转换对于某些类型是安全的,而其他类型则不安全,那么让类型直接识别该类型并让编译器拒绝不安全的操作和转换,似乎比更改用于仅适用于PODS的方式会悄悄地更改为非PODS。
超级猫

53

C ++ FQA Lite

[16.4]为什么我应该使用new而不是值得信赖的旧malloc()?

常见问题解答:new / delete调用构造函数/析构函数;new是类型安全的,malloc不是;new可以被类覆盖。

FQA:FAQ中提到的new的优点不是优点,因为构造函数,析构函数和运算符重载是垃圾(请参阅没有垃圾回收时会发生什么?),并且类型安全问题在这里确实很小(通常您有将malloc返回的void *强制转换为正确的指针类型,以将其分配给类型化的指针变量,这可能很烦人,但远非“不安全”)。

哦,使用值得信赖的旧版malloc可以使用同样值得信赖的旧版realloc。不幸的是,我们没有新的运营商续订或其他新的东西。

尽管如此,即使语言是C ++,new仍不足以证明与某种语言所使用的通用样式有所偏离。特别是,如果简单地分配对象,则具有非平凡构造函数的类将以致命的方式行为异常。那么,为什么不在整个代码中使用new?人们很少会重载运算符new,因此它可能不会过多地妨碍您。如果它们确实超载了,您可以随时要求它们停止。

抱歉,我无法抗拒。:)


7
那是暴动!谢谢。
dmckee ---前主持人小猫,

8
我不能认真对待这一评论,因为它清楚地表明了作者对C ++的偏见。C ++是一种用于创建面向性能的软件的语言,垃圾回收器只会损害其目标。我不同意您的全部回答!
Miguel 2015年

1
@Miguel你错过了这个玩笑。
Dan Bechard

50

始终在C ++中使用new。如果需要一块无类型的内存,则可以直接使用运算符new:

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

3
有趣的是,当我需要像这样的原始数据缓冲区时,我总是只分配了一个无符号字符数组。
格雷格·罗杰斯

小心的语义应该是这样的:p_var = new type(initializer); 没有大小。
Brian R. Bondy

11
如果不直接调用operator new,则不会,这需要分配字节数作为参数。
Ferruccio

1
嗯,我不确定,我从未听说过这种语法。
Brian R. Bondy

9
相反的operator newoperator delete。调用delete类型为type的表达式不是明确定义的操作void*
CB Bailey

33

使用malloc用于分配要由C-中心库和API来管理内存。对您控制的所有内容使用和(及其变体)。free newdelete[]


10
还要注意,编写良好的C库将在内部隐藏malloc和free,这是C程序员应该工作的方式。
达卡夫

@dmckee是否有一个通过malloc和free使用以c为中心的库的C ++示例?
2013年

1
@Dacav:如果C函数将接受指向该函数返回后需要继续使用的对象的指针,并且调用方将无法知道何时仍需要该对象,则对于该函数而言,这是完全合理的指定必须使用创建指针malloc。同样,如果像这样的函数strdup需要创建一个对象并将其返回给调用者,则完全合理的做法是指定free当不再需要该对象时,调用者必须在该对象上进行调用。这样的函数如何避免将其对malloc / free的使用暴露给调用者?
supercat

@supercat,因为C根本不知道对象,所以让C函数接受指向对象的指针存在固有的错误。总的来说,我相信最好的方法是在C语言中也使用围绕分配/取消分配的语义包装。如果C库要求调用者预分配和/或取消分配内存,这仍然可以接受,但灵活性较差。如果C函数正在执行此操作并要求对分配的内存拥有所有权,则隐式要求您使用malloc对其进行分配。
Dacav

@supercat我确定每个人都使用过的日常软件包的一个示例是libgmp。如果您曾经使用过任何开放源代码加密或基于这种加密的软件(很有可能),那么您可能已经使用了一个任意精度的算术库,该库需要增加和缩小自己的内部数据。这是使用初始化函数完成的...然后您会怀疑,如何在C ++中使用libgmp的C代码,而无需在C ++中重新编译呢?现在记住,(链接),想想看......为什么任何理智的人曾经malloc在C ++?
自闭症'18

31

新vs malloc()

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

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

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

4)new永不返回NULL(将抛出失败),而malloc()返回NULL

5)不能由newwhile malloc()可以处理的内存的重新分配


6
嗨,对于点4),可以指示new在失败时返回NULL。 char* ptr = new (std::nothrow) char [323232];
辛格

1
6)new从构造函数参数创建,而malloc使用size。
埃文·莫兰

还有一个new功能
Ma Ming

如果您倾向于使用C进行重新分配,我希望您使用realloc而不是malloc,并从初始化为的指针变量开始NULL。另一方面,如果您想要在C ++中可调整大小的内存块,我建议std::vector不要使用realloc...那或一个文件。
自闭症'18

19

要回答你的问题,你应该知道的区别mallocnew。区别很简单:

malloc 分配内存,同时new 分配内存并调用要为其分配内存的对象的构造函数

因此,除非限于C语言,否则永远不要使用malloc,尤其是在处理C ++对象时。那将是破坏程序的秘诀。

此外之间的差别free,并delete是完全一样的。区别在于,delete除了释放内存外,还将调用对象的析构函数。


13

malloc和之间有一个很大的区别newmalloc分配内存。这对于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

如果确实需要,可以使用use new获取未初始化的POD对象。看到其他答案更多信息,。

另一个区别是失败时的行为。如果分配内存失败,则malloc返回空指针,而new引发异常。

前者要求您在使用它之前测试返回的每个指针,而后者将始终产生有效的指针。

由于这些原因,在C ++代码中应使用new,而不是malloc。但是即使那样,您也不应使用new“公开”方式,因为它获取了以后需要释放的资源。使用时new,应立即将其结果传递到资源管理类中:

std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak

7

仅当对象的生存期与创建对象的作用域不同时才需要动态分配(这同样适用于使作用域变小或变大),并且您有特定的原因不按值存储它工作。

例如:

 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::make_unique<Class>(new Class(/*args*/)); // C++11
auto instance = std::make_unique<Class[]>(42); // C++14
auto instance = std::make_unique<Class[]>(new Class[](42)); // C++11

C ++ 17还添加了std::optional可以防止您需要内存分配的功能

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

一旦“实例”超出范围,内存就会被清理。转让所有权也很容易:

 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)

那你new什么时候还需要?从C ++ 11开始几乎没有。大部分人都使用它,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中,您必须执行手动内存管理。如果是这种情况,请尝试升级到该标准的最新版本。如果您被卡住:

 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文件的内存副本中?)
  • 您的工具阻止了您使用某些构造
  • 您需要存储不完整的类型

6

有这几件事情new确实是malloc不:

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

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


5

如果您使用不需要构造/销毁并且需要重新分配的数据(例如,大量的int),那么我相信malloc / free是一个不错的选择,因为它可以为您提供重新分配,这比new-memcpy更快-delete(在我的Linux机器上,但是我想这可能取决于平台)。如果使用不是POD且需要构造/销毁的C ++对象,则必须使用new和delete运算符。

无论如何,我不明白为什么不应该同时使用两种方法(前提是您释放了已分配的内存并删除了用new分配的对象),如果可以利用速度提升的优势(如果您要重新分配大型数组,有时会很重要)重新分配可以给您的POD)。

除非您有需要,否则应该坚持使用C ++中的new / delete。



3

如果使用的是C ++,请尝试使用new / delete代替malloc / calloc,因为它们是运算符。对于malloc / calloc,您需要包括另一个头。不要在同一代码中混用两种不同的语言。他们的工作在每种方式上都是相似的,都从哈希表中的堆段中动态分配内存。


2

new 将初始化该结构的默认值,并将其中的引用正确链接到自身。

例如

struct test_s {
    int some_strange_name = 1;
    int &easy = some_strange_name;
}

因此,new struct test_s将返回带有有效引用的初始化结构,而malloc的版本没有默认值,并且内部引用未初始化。



0

在以下情况下,我们不能使用new,因为它调用了构造函数。

class  B  {
private:
    B *ptr;
    int x;
public:
    B(int n)  {
        cout<<"B: ctr"<<endl;
        //ptr = new B;  //keep calling ctr, result is segmentation fault
        ptr = (B *)malloc(sizeof(B));
        x = n;
        ptr->x = n + 10;
    }
    ~B()  {
        //delete ptr;
        free(ptr);
        cout<<"B: dtr"<<endl;
    }
};

0

newdelete运营商可以对类和结构进行操作,而mallocfree只与内存需要转换块的工作。

使用new/delete将有助于改善代码,因为您无需将分配的内存转换为所需的数据结构。


0

考虑使用malloc / free而不是new / delete是一种罕见的情况,是当您使用realloc进行分配然后重新分配(简单的pod类型,不是对象)时,因为在C ++中没有类似的函数可以重新分配(尽管可以使用a更多的C ++方法)。


-4

malloc()用于动态分配C中的内存,而c ++中的new()完成相同的工作。因此,您不能混合使用两种语言的编码约定。如果您要求calloc和malloc()之间的区别会很好


2
可以(但几乎总是不应)malloc在C ++中使用。
interjay 2012年

1
您还错过了避免通过动态指针分配动态内存的主要要点。您只是在以其他方式使
自己陷入
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.