C++有多种分配和释放数据的方式。我知道使用malloc
时应该用free
来释放内存,而使用new
操作符时应该与delete
搭配使用。如果混合使用这两种方法(例如调用free()
释放由new
操作符创建的对象),那么将是一个错误。但是在实际编程中,我不清楚什么情况下应该使用malloc
/free
以及何时应该使用new
/delete
。
如果您是C++专家,请告诉我在这方面遵循的任何经验法则或惯例。
C++有多种分配和释放数据的方式。我知道使用malloc
时应该用free
来释放内存,而使用new
操作符时应该与delete
搭配使用。如果混合使用这两种方法(例如调用free()
释放由new
操作符创建的对象),那么将是一个错误。但是在实际编程中,我不清楚什么情况下应该使用malloc
/free
以及何时应该使用new
/delete
。
如果您是C++专家,请告诉我在这方面遵循的任何经验法则或惯例。
除非你被强制使用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
/free
和malloc
/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
new[]
比使用 std::vector
更安全吗?如果使用 new[]
,指针变为无效的唯一方式是通过显式调用 delete
,而为 std::vector
分配的内存可能会在向量被调整大小或离开作用域时失效。(请注意,在使用 new[]
时,必须考虑到可能无法调用 delete
,如果异步方法仍然处于挂起状态,则可能需要通过回调来安排删除;如果需要放弃异步操作,可能需要安排回调来执行删除)。 - supercatmalloc
。当与C++一起使用时,malloc
存在许多缺陷,而new
被定义为克服这些缺陷。
new
为C ++代码解决的缺陷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
}
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.
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;
}
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();
}
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!
struct
和 class
做几乎相同的事情是一个很棒的设计决策,现在它使得一个称为 "metaclasses" (from Herb) 的新特性成为可能。 - Rakete1111$class
)的语言版本。然而,我不确定这与class
和struct
成为同义词有什么关系。 - supercatclass
和 struct
的意义基本相同,您可以对它们进行任意转换($class
),而不必担心将 class
变成 struct
或者反过来。 - Rakete1111在 C++ 中始终使用 new。如果您需要一个未经类型化的内存块,则可以直接使用 operator new:
void *p = operator new(size);
...
operator delete(p);
operator new
的相反操作是 operator delete
。对于类型为 void*
的表达式调用 delete
不是一种明确定义的行为。 - CB Baileynew vs malloc()
1) new
是一个运算符,而malloc()
是一个函数。
2) new
调用构造函数,而malloc()
不会。
3) new
返回确切的数据类型,而malloc()
返回void *。
4) new
从不返回NULL(失败时将抛出异常),而malloc()
会返回NULL。
5) 内存重新分配由new
不处理,而malloc()
可以处理。
new
在失败时返回NULL
。
char* ptr = new (std::nothrow) char[323232];
的意思是申请一个大小为323232
的字符数组,如果申请失败,则返回NULL
。 - Singhnew
函数。 - Ma Mingrealloc
而不是malloc
,并且将指针变量初始化为NULL
。另一方面,如果你想要一个可调整大小的内存块,在C++中,我会建议你使用std::vector
而不是realloc
...或者使用文件。 - autisticmalloc
和new
之间的区别。这个区别很简单: malloc
会分配内存,而new
不仅分配内存,还会调用你要为之分配内存的对象的构造函数。free
和delete
之间的区别也相当类似。不同之处在于,delete
除了释放内存之外,还会调用你的对象的析构函数。只在需要使用c-centric库和API来管理内存时,使用malloc
和free
。在你可以控制的所有情况下,使用new
和delete
(以及[]
变体)。
malloc
创建是完全合理的。同样地,如果像strdup
这样的函数需要创建并返回一个对象给调用者,那么要求调用者在不再需要该对象时调用free
也是完全合理的。这些函数如何避免向调用者公开它们对malloc/free
的使用呢? - supercatchar c;
这个变量都表示一个对象。不同之处在于,C++中的一些(但不是全部)对象也是多态的(毕竟C++是面向对象的)。不要误以为只有面向对象的代码才能使用对象。 - autisticmalloc
和new
之间有一个很大的区别。 malloc
分配内存。这对于C语言来说是可以的,因为在C语言中,一块内存就是一个对象。new
在分配内存的同时创建一个对象并将其放置在该内存位置上。对于非POD类型,这意味着调用构造函数。non_pod_type* p = (non_pod_type*) malloc(sizeof *p);
您获得的指针无法被解引用,因为它没有指向任何对象。在使用它之前,您需要调用构造函数(可以使用放置 new
实现)。
另一方面,如果您执行以下操作:
non_pod_type* p = new non_pod_type();
new
创建了一个对象。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
会抛出异常。new
,而不是malloc
。但即使这样,也不应该“公开地”使用new
,因为它会获取需要稍后释放的资源。当使用new
时,应立即将其结果传递给资源管理类:std::unique_ptr<T> p = std::unique_ptr<T>(new T()); // this won't leak
只有在对象的生命周期应与创建它的范围不同(缩小或扩大范围都适用)且存储到值中不起作用的特定原因时,才需要动态分配。
例如:
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文件?);
-您有工具阻止您使用某些构造;
-您需要存储不完整类型。malloc
或new
调用。 - Russell Trahan在一些方面,new
比 malloc
更好:
new
通过调用对象的构造函数来构造该对象。new
不需要分配的内存进行类型转换。因此,如果您使用 malloc
,则需要显式执行上述操作,这并不总是实用的。此外,new
可以重载,但是 malloc
不能。