2017-01-27 10 views
8

일부 상황부터 살펴 보겠습니다.배치 '새`는 기본 저장소 값에 의존할까요?

사용자 정의 메모리 풀은 다음과 유사한 코드를 사용했다 :

struct FastInitialization {}; 

template <typename T> 
T* create() { 
    static FastInitialization const F = {}; 

    void* ptr = malloc(sizeof(T)); 
    memset(ptr, 0, sizeof(T)); 
    new (ptr) T(F); 
    return reinterpret_cast<T*>(ptr); 
} 

생각 FastInitialization를 호출 할 때, 생성자는 저장 이미 제로 초기화 있다고 가정 따라서 만 초기화 할 수 있다는 것입니다 다른 가치가 필요한 회원.

GCC (6.2 및 6.3, 적어도) 그러나에 개막에 "흥미로운"최적화를 가지고

struct Memset { 
    Memset(FastInitialization) { memset(this, 0, sizeof(Memset)); } 

    double mDouble; 
    unsigned mUnsigned; 
}; 

Memset* make_memset() { 
    return create<Memset>(); 
} 

는 아래로 컴파일합니다.

make_memset(): 
     sub  rsp, 8 
     mov  edi, 16 
     call malloc 
     mov  QWORD PTR [rax], 0 
     mov  QWORD PTR [rax+8], 0 
     add  rsp, 8 
     ret 

그러나 :

struct DerivedMemset: Memset { 
    DerivedMemset(FastInitialization f): Memset(f) {} 

    double mOther; 
    double mYam; 
}; 

DerivedMemset* make_derived_memset() { 
    return create<DerivedMemset>(); 
} 

아래로 컴파일 :

make_derived_memset(): 
     sub  rsp, 8 
     mov  edi, 32 
     call malloc 
     mov  QWORD PTR [rax], 0 
     mov  QWORD PTR [rax+8], 0 
     add  rsp, 8 
     ret 

즉, 기본에 해당하는 부분 인 struct의 처음 16 바이트 만 초기화되었습니다. 디버깅 정보는 memset(ptr, 0, sizeof(T));에 대한 호출이 완전히 제거되었음을 확인합니다.

make_derived_memset():    # @make_derived_memset() 
     push rax 
     mov  edi, 32 
     call malloc 
     xorps xmm0, xmm0 
     movups xmmword ptr [rax + 16], xmm0 
     movups xmmword ptr [rax], xmm0 
     pop  rcx 
     ret 

그래서 GCC와 연타의 행동이 다르며, 문제가된다 : GCC 권리 인 반면에

, 모두 ICC와 연타 모두 전체 크기에 memset 전화, 여기에 연타의 결과입니다 더 나은 어셈블리를 만들거나 Clang right와 GCC 버그가 있습니까?


또는 언어 변호의 관점에서 : 생성자가 할당 된 스토리지에 저장된 이전 값에 의존 할 수

있는 상황에서?

참고 : 게재 위치는 new이며 문제가 있다고 가정합니다.

+0

코드가 memset (ptr, 0, sizeof (T)) 및 sizeof (T) = 32를 실행하고 16 바이트 만 0이면 분명히 옳지 않습니다. create가 호출 된 후 멤버 변수를 읽는 코드를 추가하려고 했습니까? 그들의 가치는 무엇입니까? –

+0

나는이 질문이 너의 훨씬 더 넓은 질문에 대한 답이 아니라는 것을 안다; 하지만 0으로 초기화하는 경우 : 당신은'calloc'을 시도해 보았습니다. 여기서 memset으로 일어날 수있는 0으로 컴파일러에서 0을 초기화하지 말아야합니까? –

+0

@SvenNilsson : 값은'memset' 호출 이전에 포함 된 메모리로부터 남겨졌으며, 실제 코드에는 포인터가 있었으므로 이제 해제 된 메모리에 포인터가 매달려있었습니다. –

답변

10

게재 위치 new은 기본 저장 값을 사용합니까?

아니요, 그렇지 않을 수도 있습니다. [dcl.init]에서 :

개체에 대해 지정된 초기화 프로그램이 없으면 개체가 기본 초기화됩니다.자동 또는 동적 저장 기간을 갖는 객체 에 대한 저장 공간이 확보되면 객체는 불확정 값을 가지며 객체에 대해 초기화가 수행되지 않으면 객체는 해당 값이 대체 될 때까지 불확정 값을 유지합니다 (5.18).

불확정 값은 불확정성을 의미합니다. 배치 표현식의 경우 이전 메모리가 반드시 유지되어야 함을 의미하지는 않습니다. 컴파일러는 메모리에 원하는 모든 작업을 수행 할 수 있습니다.이 작업에는 아무런 제한도 없습니다.

어떤 상황에서 생성자는 할당 된 저장소에 저장된 이전 값을 신뢰할 수 있습니까?

동작이 이다 나열 경우 [dcl.init]에 후속 단락되지 불확정 값을 생성하지만 단지 부호 좁은 문자 유형으로 할 때 정의.

따라서 어떠한 경우에도 적용되지 않습니다.

+0

여기서 흥미로운 점은 메모리가 불확정 상태에 있도록 지원되지 않는다는 것입니다 : 명시 적으로 0으로 초기화되었지만 GCC는 생성자가 실행될 예정 이었기 때문에 0 초기화가 불필요하다는 것을 분명히 결정했습니다. –

+0

@MatthieuM. 물론, 생성자 이후는 불확정하므로 유효한 최적화입니다. – Barry

+0

그게 내가 의심하는 바, POD/사소한 레이아웃/표준 레이아웃 유형이 면제 될 수 있다는 징후가 있는지 아십니까? –

1

GCC와 Clang이 모두 정확합니다. - 자신의 데이터 멤버를 DerivedMemset으로 초기화하지 않은 채로두면 해당 값에 액세스하자마자 정의되지 않은 동작이 발생합니다. 따라서 컴파일러는 생성자가 호출되기 직전에 해당 필드가 차지할 저장소 범위의 비트 패턴을 남겨 둘 라이센스를 제공받습니다.

+0

언어 변호사 태그에 대한 질문에 대한 답변에서 일반적으로 C++ 표준의 인용 부호로 자신의 주장을 뒷받침 할 것으로 예상됩니다. 구체적으로,이 경우 대답은 Trivial Types ('memcpy'에 의해 복사 될 수 있음), 표준 레이아웃 유형 및 POD ('DerivedMemset'이 그 중 하나로 자격이되는지 여부 3) vs "본격적인" 수업. –

+0

사실이 답변은 실제로 의미가 있습니다. 새로운 배치 전에 메모리를 0으로 만드는 수동 작업이 컴파일러에 의해 무시 될 수 있다는 것은 예상치 못한 일입니다. 그러나 표준에서 컴파일러가 초기화되지 않은 멤버에서 임의의 종류의 임의의 정크를 만들 수 있다고 말하면 여전히 괜찮습니다. –