C++ 新建[],删除[]复杂性
我已经知道,C++ 新建[],删除[]复杂性,c++,memory-management,time-complexity,C++,Memory Management,Time Complexity,我已经知道,new[]操作符首先分配内存,然后为每个元素调用构造函数,delete[]操作符首先为每个元素调用析构函数,然后释放内存,因此,它们的时间复杂度都是O(n) 但是如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是仅仅是O(1) 例如,如果我有两个类: class foo { public: int a; foo() { a = 0; // more stuff } ~foo(
new[]
操作符首先分配内存,然后为每个元素调用构造函数,delete[]
操作符首先为每个元素调用析构函数,然后释放内存,因此,它们的时间复杂度都是O(n)
但是如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是仅仅是O(1)
例如,如果我有两个类:
class foo
{
public:
int a;
foo()
{
a = 0;
// more stuff
}
~foo()
{
a = 1;
// some useful stuff here
}
};
class boo
{
public:
int a;
};
我创建了两个数组,如下所示:
int n = 1000;
foo* pfoo = new foo[n];
boo* pboo = new boo[n];
我很确定第一个new
调用的复杂性是O(n),但是第二个呢?new
会只分配必要的内存,就这样,还是会为每个元素调用一些默认构造函数(我不确定C++中是否确实存在这样的东西)
对于删除
,还有同样的问题:
delete [] pfoo;
delete [] pboo;
当我删除第二个数组时,复杂度仍然是O(n),还是delete
只是在O(1)复杂度中释放内存
但是如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是仅仅是O(1)
成员本身可能仍然有析构函数。简而言之,对于POD,
delete[]
将是O(1)。这取决于您的确切语法:
auto x = new unsigned[2];
auto y = new unsigned[2]();
::std::cout << x[0] << "\n" << x[1] << "\n" << y[0] << "\n" << y[1] << "\n";
delete[] x;
delete[] y;
因为一个将被默认初始化,另一个值将被初始化
另一方面,
delete[]
更容易理解:如果您的数据类型具有析构函数,则会调用它。内置(因此POD)类型通常不知道。当您不知道时,最好使用汇编输出。例如,假设这是要比较的代码
class foo
{
public:
int a;
foo()
{
a = 0;
// more stuff
}
~foo()
{
a = 1;
// some useful stuff here
}
};
class boo
{
public:
int a;
};
void remove_foo(foo* pfoo) {
delete [] pfoo;
}
void remove_boo(boo *pboo) {
delete [] pboo;
}
当使用gcc进行优化编译时(Clang提供类似的输出),您会得到以下结果
.file "deleter.cpp"
.text
.p2align 4,,15
.globl _Z10remove_fooP3foo
.type _Z10remove_fooP3foo, @function
_Z10remove_fooP3foo:
.LFB6:
.cfi_startproc
testq %rdi, %rdi
je .L1
movq -8(%rdi), %rax
leaq (%rdi,%rax,4), %rax
cmpq %rax, %rdi
je .L4
.p2align 4,,10
.p2align 3
.L6:
subq $4, %rax
movl $1, (%rax)
cmpq %rax, %rdi
jne .L6
.L4:
subq $8, %rdi
jmp _ZdaPv
.p2align 4,,10
.p2align 3
.L1:
rep ret
.cfi_endproc
.LFE6:
.size _Z10remove_fooP3foo, .-_Z10remove_fooP3foo
.p2align 4,,15
.globl _Z10remove_booP3boo
.type _Z10remove_booP3boo, @function
_Z10remove_booP3boo:
.LFB7:
.cfi_startproc
testq %rdi, %rdi
je .L8
jmp _ZdaPv
.p2align 4,,10
.p2align 3
.L8:
rep ret
.cfi_endproc
.LFE7:
.size _Z10remove_booP3boo, .-_Z10remove_booP3boo
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
很容易看出,对于foo
它调用析构函数,但是对于boo
它直接调用delete[]
函数(\u ZdaPv
在名称混乱之后)。这在没有优化的情况下也会发生。代码更长,因为方法实际上是输出的,但仍然值得注意的是,delete[]
直接为boo
调用
.file "deleter.cpp"
.section .text._ZN3fooD2Ev,"axG",@progbits,_ZN3fooD5Ev,comdat
.align 2
.weak _ZN3fooD2Ev
.type _ZN3fooD2Ev, @function
_ZN3fooD2Ev:
.LFB4:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movq %rdi, -8(%rbp)
movq -8(%rbp), %rax
movl $1, (%rax)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE4:
.size _ZN3fooD2Ev, .-_ZN3fooD2Ev
.weak _ZN3fooD1Ev
.set _ZN3fooD1Ev,_ZN3fooD2Ev
.text
.globl _Z10remove_fooP3foo
.type _Z10remove_fooP3foo, @function
_Z10remove_fooP3foo:
.LFB6:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
pushq %rbx
subq $24, %rsp
.cfi_offset 3, -24
movq %rdi, -24(%rbp)
cmpq $0, -24(%rbp)
je .L3
movq -24(%rbp), %rax
subq $8, %rax
movq (%rax), %rax
leaq 0(,%rax,4), %rdx
movq -24(%rbp), %rax
leaq (%rdx,%rax), %rbx
.L6:
cmpq -24(%rbp), %rbx
je .L5
subq $4, %rbx
movq %rbx, %rdi
call _ZN3fooD1Ev
jmp .L6
.L5:
movq -24(%rbp), %rax
subq $8, %rax
movq %rax, %rdi
call _ZdaPv
.L3:
addq $24, %rsp
popq %rbx
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE6:
.size _Z10remove_fooP3foo, .-_Z10remove_fooP3foo
.globl _Z10remove_booP3boo
.type _Z10remove_booP3boo, @function
_Z10remove_booP3boo:
.LFB7:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movq %rdi, -8(%rbp)
cmpq $0, -8(%rbp)
je .L7
movq -8(%rbp), %rax
movq %rax, %rdi
call _ZdaPv
.L7:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE7:
.size _Z10remove_booP3boo, .-_Z10remove_booP3boo
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
这也适用于new[]
<代码>\u Znam直接调用,无需构建对象,甚至无需优化
通常,这意味着自定义构造函数或析构函数意味着new[]
和delete[]
不会在固定时间内执行。但如果没有,编译器不会尝试调用这些对象的构造函数或析构函数,它们将是POD数据类型,这意味着构造这些对象是简单的malloc调用。也有一些例外(涉及各种优化),但通常带有构造函数/析构函数的代码将是O(N),没有构造函数的代码将是O(1),假设O(1)new[]
/delete[]
实现。MyClass*p=static_cast(::操作符new(sizeof(MyClass[N]);
MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));
为N个对象分配内存,但不构造它们。这样,复杂性将与malloc()相同。它显然比分配和构造复杂类的对象更快。我自己也试着检查汇编代码,但这就像是一种外来语言。我使用VisualC++,但是我认为它的编译器在这种情况下的行为与GCC几乎相同。非常感谢您花时间记录您的答案。
MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));