新增[],删除[]复杂性

new [], delete [] complexity

本文关键字:复杂性 删除 新增      更新时间:2023-10-16

我已经知道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:的相同问题

delete [] pfoo;
delete [] pboo;

当我删除第二个数组时,复杂性仍然是O(n),还是delete只是以O(1)复杂性释放内存?

如果您不知道,最好使用汇编输出。例如,让我们假设这是要进行比较的代码。

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 []函数(名称篡改后的_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 []。直接调用_Znam,无需构造对象,甚至无需优化。

通常,这意味着自定义构造函数或析构函数意味着new []delete []不会在恒定时间内执行。但如果没有,编译器就不会尝试为这些对象调用构造函数或析构函数,它们将是POD数据类型,这意味着构造这些对象是类似malloc的简单调用。有一些例外(涉及各种优化),但通常具有构造函数/析构函数的代码将为O(N),而不具有构造函数/析构函数的则为O(1),假设实现为O(2)new []/delete []

这取决于您的确切语法:

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;

给出输出(在我的机器上):

3452816845
3452816845
0
0

因为一个值将被默认初始化,另一个值则被初始化。

另一方面,delete[]更容易理解:如果你的数据类型有一个析构函数,它就会被调用。内置(以及POD)类型通常不会。

MyClass *p = static_cast<MyClass*> (::operator new (sizeof(MyClass[N])));

为N个对象分配内存,而不构造它们。这样一来,复杂性将与malloc()相同。它显然会比分配和构造复杂类的对象更快。

但是,如果我有一个类,我没有为它定义任何构造函数/析构函数,那么它的复杂性仍然是O(n),还是只是O(1)?

成员本身可能仍然具有析构函数。简而言之,对于POD,delete[]将是O(1)。