new [], delete [] complexity

后端 未结 4 1398
眼角桃花
眼角桃花 2021-02-07 10:23

I already know that the new[] operator first allocates memory and then calls the constructor for each element and that the delete[] operator first call

4条回答
  •  时光说笑
    2021-02-07 10:36

    When you don't know, it's great idea to use assembly output. For example, let's assume this is the code to compare.

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

    When compiling with optimizations using gcc (Clang gives similar output), you get the following result.

        .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
    

    It's easy to tell that for foo it calls destructor, but for boo it directly calls delete [] function (_ZdaPv after name mangling). This also happens without optimizations. The code is longer, because methods are actually output, but it's still noticeable that delete [] is called directly for 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
    

    This also applies to new []. _Znam is called directly, without constructing objects, even without optimizations.

    Generally, that means custom constructors or destructors mean that new [] and delete [] won't be executed in constant time. But if there aren't, the compiler doesn't try to call constructors or destructors for these, and they will be POD data types, which means that constructing these objects is simple malloc-like call. There are some exceptions (involving various optimizations), but usually code with constructors/destructors will be O(N), and without will be O(1), assuming O(1) new []/delete [] implementation.

提交回复
热议问题