是 C++ 语句的 Big-O 'delete [] Q;' O(1) 还是 O(n)?

Posted

技术标签:

【中文标题】是 C++ 语句的 Big-O \'delete [] Q;\' O(1) 还是 O(n)?【英文标题】:Is Big-O of the C++ statement 'delete [] Q;' O(1) or O(n)?是 C++ 语句的 Big-O 'delete [] Q;' O(1) 还是 O(n)? 【发布时间】:2013-05-04 12:28:01 【问题描述】:

标题是不言自明的。很简单的问题。我认为是 O(n),但想在明天的决赛之前进行验证。

【问题讨论】:

我给你个提示:std::string::~string() 密切相关:***.com/q/16420357/179910 这很好:parashift.com/c++-faq-lite/intrinsics-delete-array.html 真正的问题是:n 是什么? 另见this newer question 和my answer 那里... 【参考方案1】:

我同意这取决于,但我们只讨论释放 X 字节的内存而不担心析构函数。

一些内存分配器为“小”(1 到 500 字节)对象保留空闲列表。插入列表是 O(1)。如果所有线程都有一个空闲列表,那么它需要获取一个互斥锁。获取互斥锁通常涉及多达 1000 次“旋转”,然后可能是系统调用(这非常昂贵)。一些分配器使用线程本地存储为每个线程提供空闲列表,因此它们不是互斥体获取。

对于中等(500 到 60000 字节)大小的对象,许多分配器会进行块合并。也就是说,他们检查相邻块是否也是空闲的,然后将 2 或 3 个空闲块合并为 1 个更大的空闲块。合并是 O(1),但同样需要获取互斥体。

对于大块,一些分配器使用系统调用来获取内存。所以释放内存也是一个系统调用。

【讨论】:

【参考方案2】:

简短的回答是……视情况而定。

如果Q 是指向具有析构函数的对象数组的指针,那么delete[] Q 将需要调用所有这些析构函数。这将调用 O(n) 析构函数,其中 n 是数组中元素的数量。另一方面,如果Q 指向一个没有析构函数的对象数组(例如,ints 或简单的structs),则不需要调用析构函数,这只需 O( 1) 时间。

现在请注意,这些析构函数不必每次都在 O(1) 时间内运行。例如,如果对象是std::vector 对象,那么每个析构函数又必须触发更多的释放。在不了解这些对象是什么的情况下,我们只能说,如果调用了析构函数,那么如果析构函数是微不足道的,则将调用 0 个析构函数,否则将调用 O(n) 个析构函数。

但这忽略了堆分配器如何工作的实现细节。释放一块内存可能需要 O(log K) 时间,其中 K 是已分配块的总数,或者无论有多少内存块,都可能需要 O(1) 时间,或者可能需要O(log log K) 等。不知道分配器是如何工作的,你真的无法确定。

简而言之,如果您只专注于在将块交还给内存分配器之前清理对象所需的工作,那么如果存储的对象具有析构函数,则会调用 O(n) 个析构函数,否则会调用 0 个析构函数。这些析构函数可能需要很长时间才能完成。然后,将内存块重新引入内存分配器会产生成本,这可能需要花费自己的时间。

希望这会有所帮助!

【讨论】:

@Ethan Barron 现在把它写在一张干净的纸上。把它放在你的衬衫下面。在分发试卷时,迅速采取行动,将试卷放在试卷下面的衬衫下面。祝你好运。 我想补充一些很多人错过的重要内容。数组包含的对象不需要定义析构函数。重要的是析构函数(定义的或默认的)是平凡的。也就是说,如果一个类有一个 vector 作为成员,那么默认的析构函数是不平凡的并且会运行,即使没有显式定义析构函数

以上是关于是 C++ 语句的 Big-O 'delete [] Q;' O(1) 还是 O(n)?的主要内容,如果未能解决你的问题,请参考以下文章

Java中有类似C++ 的delete ,free语句来释放资源吗

C++智能指针总结

C++ 关于内存泄露问题。内存泄露是指用new 分配的内存没有用delete释放,如果未释放会有啥后果?

使用 NSpredicate 过滤 NSArray 的 Big-O 运行时

在 mongoDB 中搜索索引数据的复杂度(Big-O)是多少?

用于迭代列表中步骤的 Big-O 表示法-Python