Как работает размещение новых?

Я написал следующее, думая, что это должно быть segfault во время выполнения. Однако это не так, он работает нормально, и я не понимаю, почему.

#include <cstdlib>
#include <cstdio>
#include <new>

struct MyStruct
{
    double *a;

    MyStruct()
        : a(NULL) 
        { printf("Default constructor\n"); }
    MyStruct( double *b )
        : a(b) 
        {}
    MyStruct( const MyStruct& other )
    {
        printf("Copy-constructor\n");
        if ( a != NULL && *a != 3.14 )
            a = other.a;
    }
};

int main()
{
    double num = 3.14;
    MyStruct obj( &num );

    void *ptr = ::operator new( sizeof(MyStruct) );
    new (ptr) MyStruct(obj);

    delete (MyStruct*) ptr; // Calls ~MyStruct
}

Выход:

Copy-constructor

Когда я пишу void *ptr = ::operator new( sizeof(MyStruct) ); Я знаю, что это только выделяет память, и не должен вызывать конструктор по умолчанию. И это не похоже: хорошо.

Когда я пишу new (ptr) MyStruct(obj);Я бы ожидал, что это будет сегфо, если это сработает так, как я думал. Я думаю, что это эквивалентно ( (MyStruct*) ptr )->MyStruct(obj), Если это так, линия if ( a != NULL && *a != 3.14 ) должен достичь *a != 3.14 и Segfault, потому что a не был инициализирован.

Моя проблема в том что a похоже, что он не был инициализирован (потому что не было вывода "Конструктор по умолчанию"), но предыдущий по-прежнему не работает по умолчанию. Что мне не хватает?


Вот сгенерированный код сборки (я не знаю, как его прочитать):

    .file   "placement_new.cpp"
    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI0_0:
    .quad   4614253070214989087     # double 3.1400000000000001
    .text
    .globl  main
    .align  16, 0x90
    .type   main,@function
main:                                   # @main
    .cfi_startproc
# BB#0:
    push    rbp
.Ltmp2:
    .cfi_def_cfa_offset 16
.Ltmp3:
    .cfi_offset rbp, -16
    mov rbp, rsp
.Ltmp4:
    .cfi_def_cfa_register rbp
    sub rsp, 48
    lea rdi, qword ptr [rbp - 24]
    lea rsi, qword ptr [rbp - 16]
    movsd   xmm0, qword ptr [.LCPI0_0]
    mov dword ptr [rbp - 4], 0
    movsd   qword ptr [rbp - 16], xmm0
    call    _ZN8MyStructC2EPd
    movabs  rdi, 8
    call    _Znwm
    mov qword ptr [rbp - 32], rax
    mov rax, qword ptr [rbp - 32]
    cmp rax, 0
    mov qword ptr [rbp - 40], rax # 8-byte Spill
    je  .LBB0_2
# BB#1:
    lea rsi, qword ptr [rbp - 24]
    mov rax, qword ptr [rbp - 40] # 8-byte Reload
    mov rdi, rax
    call    _ZN8MyStructC2ERKS_
.LBB0_2:
    mov rax, qword ptr [rbp - 32]
    cmp rax, 0
    mov qword ptr [rbp - 48], rax # 8-byte Spill
    je  .LBB0_4
# BB#3:
    mov rax, qword ptr [rbp - 48] # 8-byte Reload
    mov rdi, rax
    call    _ZdlPv
.LBB0_4:
    mov eax, dword ptr [rbp - 4]
    add rsp, 48
    pop rbp
    ret
.Ltmp5:
    .size   main, .Ltmp5-main
    .cfi_endproc

    .section    .text._ZN8MyStructC2EPd,"axG",@progbits,_ZN8MyStructC2EPd,comdat
    .weak   _ZN8MyStructC2EPd
    .align  16, 0x90
    .type   _ZN8MyStructC2EPd,@function
_ZN8MyStructC2EPd:                      # @_ZN8MyStructC2EPd
    .cfi_startproc
# BB#0:
    push    rbp
.Ltmp8:
    .cfi_def_cfa_offset 16
.Ltmp9:
    .cfi_offset rbp, -16
    mov rbp, rsp
.Ltmp10:
    .cfi_def_cfa_register rbp
    mov qword ptr [rbp - 8], rdi
    mov qword ptr [rbp - 16], rsi
    mov rsi, qword ptr [rbp - 8]
    mov rdi, qword ptr [rbp - 16]
    mov qword ptr [rsi], rdi
    pop rbp
    ret
.Ltmp11:
    .size   _ZN8MyStructC2EPd, .Ltmp11-_ZN8MyStructC2EPd
    .cfi_endproc

    .section    .rodata.cst8,"aM",@progbits,8
    .align  8
.LCPI2_0:
    .quad   4614253070214989087     # double 3.1400000000000001
    .section    .text._ZN8MyStructC2ERKS_,"axG",@progbits,_ZN8MyStructC2ERKS_,comdat
    .weak   _ZN8MyStructC2ERKS_
    .align  16, 0x90
    .type   _ZN8MyStructC2ERKS_,@function
_ZN8MyStructC2ERKS_:                    # @_ZN8MyStructC2ERKS_
    .cfi_startproc
# BB#0:
    push    rbp
.Ltmp14:
    .cfi_def_cfa_offset 16
.Ltmp15:
    .cfi_offset rbp, -16
    mov rbp, rsp
.Ltmp16:
    .cfi_def_cfa_register rbp
    sub rsp, 32
    lea rax, qword ptr [.L.str]
    mov qword ptr [rbp - 8], rdi
    mov qword ptr [rbp - 16], rsi
    mov rsi, qword ptr [rbp - 8]
    mov rdi, rax
    mov al, 0
    mov qword ptr [rbp - 24], rsi # 8-byte Spill
    call    printf
    mov rsi, qword ptr [rbp - 24] # 8-byte Reload
    cmp qword ptr [rsi], 0
    mov dword ptr [rbp - 28], eax # 4-byte Spill
    je  .LBB2_3
# BB#1:
    movsd   xmm0, qword ptr [.LCPI2_0]
    mov rax, qword ptr [rbp - 24] # 8-byte Reload
    mov rcx, qword ptr [rax]
    movsd   xmm1, qword ptr [rcx]
    ucomisd xmm1, xmm0
    jne .LBB2_2
    jp  .LBB2_2
    jmp .LBB2_3
.LBB2_2:
    mov rax, qword ptr [rbp - 16]
    mov rax, qword ptr [rax]
    mov rcx, qword ptr [rbp - 24] # 8-byte Reload
    mov qword ptr [rcx], rax
.LBB2_3:
    add rsp, 32
    pop rbp
    ret
.Ltmp17:
    .size   _ZN8MyStructC2ERKS_, .Ltmp17-_ZN8MyStructC2ERKS_
    .cfi_endproc

    .type   .L.str,@object          # @.str
    .section    .rodata.str1.1,"aMS",@progbits,1
.L.str:
    .asciz  "Copy-constructor\n"
    .size   .L.str, 18


    .ident  "Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)"
    .section    ".note.GNU-stack","",@progbits

2 ответа

Решение

Чтение переменной, которая не была инициализирована, является неопределенным поведением. Тогда не исключено, что ваш компилятор устанавливает значение указателя в NULL перед присваиванием, что приведет к короткому замыканию на if заявление в вашем конструкторе копирования и, таким образом, *a никогда не будет казнен.

Звонок на размещение new все в порядке: что должно быть не так: он получает достаточно памяти, чтобы поместить в него объект. Конечно, он должен использовать конструктор копирования, если вы вызываете его obj в качестве параметра. Появляется ли этот вывод, однако, не в воздухе: printf() буферизует его память и, поскольку вы вызываете неопределенное поведение после этой конструкции, вызывая delete (MyStruct*)ptr;т. е. на указатель, не полученный без размещения new, код может легко потерпеть крах до того, как библиотека очистит буфер (это печатает, что конструктор копирования использовался в моей системе).

Чтобы правильно уничтожить ваш объект, вам нужно использовать что-то вроде этого:

MyStruct* mptr = new(ptr) MyStruct(obj);
mptr->~MyStrucT();
operator delete(ptr);

На самом деле, существует также неопределенное поведение в отношении члена a во время создания копии: члены не копируются неявно. То есть вы обращаетесь к неинициализированной памяти в конструкторе копирования, который также может делать все, что захочет.

Другие вопросы по тегам