기본값, 값 및 제로 초기화 혼란


89

값 및 기본값 및 제로 초기화에 대해 매우 혼란 스럽습니다. 특히 다른 표준 C ++ 03C ++ 11 (및 C ++ 14 ) 을 위해 시작될 때 .

나는 누군가가 도움을 줄 수 있다면 많은 사용자에게 도움이 될 것이므로 더 일반적으로 만들기 위해 Value- / Default- / Zero- Init C ++ 98C ++ 03 정말 좋은 답변을 인용하고 확장하려고 합니다. 언제 어떤 일이 발생하는지에 대한 좋은 개요를 얻기 위해 간격이 필요합니까?

한마디로 예제를 통한 전체 통찰력 :

때로는 new 연산자에 의해 반환 된 메모리가 초기화되고, 때로는 새로 만드는 유형이 POD (일반 이전 데이터) 인지 또는 POD 멤버를 포함하고있는 클래스 인지 여부에 따라 초기화되지 않을 수 있습니다. 컴파일러 생성 기본 생성자.

  • 에서 1998 ++ C : 초기화의 2 종류가 있습니다 제로기본 초기화는
  • 에서 2003 ++ C 초기화의 제 3 유형의 값을 초기화가 추가되었습니다.
  • 에서 C ++ 2011 / C ++ 2014리스트 초기화를 첨가하고 규칙 부가가치 / default- / 제로 초기화가 조금 변경되었습니다.

취하다:

struct A { int m; };                     
struct B { ~B(); int m; };               
struct C { C() : m(){}; ~C(); int m; };  
struct D { D(){}; int m; };             
struct E { E() = default; int m;}; /** only possible in c++11/14 */  
struct F {F(); int m;};  F::F() = default; /** only possible in c++11/14 */

C ++ 98 컴파일러에서 다음이 발생해야합니다 .

  • new A -불확실한 값 ( APOD 임)
  • new A()-제로 초기화
  • new B -기본 구성 ( B::m초기화되지 않음, BPOD가 아님)
  • new B()-기본 구성 ( B::m초기화되지 않음)
  • new C -기본 구성 ( C::m0으로 초기화 됨, CPOD가 아님)
  • new C()-기본 구성 ( C::m0으로 초기화 됨)
  • new D -기본 구성 ( D::m초기화되지 않음, DPOD가 아님)
  • new D()- 기본 구성? ( D::m초기화되지 않음)

C ++ 03 준수 컴파일러에서는 다음과 같이 작동해야합니다.

  • new A -불확실한 값 ( APOD 임)
  • new A() -value-initialize A, POD이므로 0으로 초기화됩니다.
  • new BB::m-default -initializes ( 초기화되지 B않은 상태로두고 POD가 아님)
  • new B() - 값 초기화합니다 B사용자 정의 반대로 기본 ctor에 이후의 모든 필드를 제로 - 초기화 컴파일러가 생성됩니다.
  • new CC-default -initializes , 기본 ctor를 호출합니다. ( C::m0으로 초기화 됨, CPOD가 아님)
  • new C() - C기본 ctor를 호출하는 value-initializes . ( C::m0으로 초기화 됨)
  • new D -기본 구성 ( D::m초기화되지 않음, DPOD가 아님)
  • new D() - 가치 초기화 D? , 기본 ctor ( D::m초기화되지 않음) 를 호출합니다.

기울임 꼴 값 및? 불확실성이 있습니다.이 문제를 해결하는 데 도움을주세요 :-)

C ++ 11 준수 컴파일러에서는 다음과 같이 작동해야합니다.

??? (여기서 시작하면 어쨌든 잘못 될 것입니다)

C ++ 14 준수 컴파일러에서는 다음과 같이 작동해야합니다. ??? (여기서 시작하면 어쨌든 잘못 될 것입니다.) (답변에 따른 초안)

  • new A -기본 초기화 A, 컴파일러 생성. ctor, ( A::m초기화되지 않은 상태) ( APOD 임)

  • new A() -값 초기화 A, [dcl.init] / 8의 2. 지점 이후 0으로 초기화 됨

  • new B -기본-초기화 B, 컴파일러 생성. ctor, ( B::m초기화되지 않은 리프 ) ( Bis non-POD)

  • new B() - 값 초기화합니다 B사용자 정의 반대로 기본 ctor에 이후의 모든 필드를 제로 - 초기화 컴파일러가 생성됩니다.

  • new CC-default -initializes , 기본 ctor를 호출합니다. ( C::m0으로 초기화 됨, CPOD가 아님)

  • new C() - C기본 ctor를 호출하는 value-initializes . ( C::m0으로 초기화 됨)

  • new D -기본 초기화 D( D::m초기화되지 않음, DPOD가 아님)

  • new D() - D기본 ctor를 호출하는 value-initializes ( D::m초기화되지 않음)

  • new E -comp E를 호출하는 default-initializes . gen. ctor. ( E::m초기화되지 않음, E는 POD가 아님)

  • new E() -값-초기화 E, [dcl.init] / 8의E 2 포인트 이후 0으로 초기화 됨 )

  • new F -comp F를 호출하는 default-initializes . gen. ctor. ( F::m초기화되지 않음, FPOD가 아님)

  • new F()-value -initializes F, 1부터 기본값으로 초기화 F 됩니다. [dcl.init] / 8에서 지정합니다 ( Fctor 함수는 사용자가 선언하고 첫 번째 선언에서 명시 적으로 기본값이 설정되거나 삭제되지 않은 경우 사용자가 제공합니다. Link )



1
내가 알 수있는 한,이 예제에서 C ++ 98과 C ++ 03에는 차이가 있습니다. 이 문제는 N1161 (해당 문서의 이후 개정판이 있음) 및 CWG DR # 178에 설명되어있는 것으로 보입니다 . 표현 으로 인해 새로운 기능과 POD의 새로운 사양에 C ++ (11)의 변화에 필요한, 그리고 그것 때문에 C ++ 11 표현의 결함으로 C ++ (14)에 다시 변경하지만,이 경우의 효과는 변경되지 않습니다 .
dyp apr

3
지루하지만 struct D { D() {}; int m; };목록에 포함 할 가치가 있습니다.
Yakk-Adam Nevraumont 2015

답변:


24

C ++ 14 new는 [expr.new] / 17에서 생성 된 객체의 초기화를 지정합니다 (C ++ 11의 [expr.new] / 15, 그 당시에는 메모가 메모가 아니라 표준 텍스트였습니다).

새로운 표현 형식의 개체를 만들고 T다음과 같이 해당 객체를 초기화한다 :

  • 경우 새로운 초기화가 생략 된 객체는 기본 초기화 (8.5). [ 참고 : 초기화가 수행되지 않으면 개체에 불확실한 값이 있습니다. — 끝 참고 ]
  • 그렇지 않으면 new-initializer직접 초기화에 대한 8.5의 초기화 규칙에 따라 해석됩니다 .

기본 초기화는 [dcl.init] / 7에 정의되어 있습니다 (C ++ 11의 경우 / 6, 단어 자체가 동일한 효과를 가짐).

유형의 객체 를 기본 초기화T 한다는 것은 다음을 의미합니다.

  • 경우 T에 (아마도 이력서 자격) 클래스 유형 (9 절), 기본 생성자 (12.1)이다 T(라고하며 경우 초기화가 잘못 형성되는 T디폴트 생성자 또는 오버로드 확인 (모호한 또는 13.3) 결과가 없습니다 초기화의 컨텍스트에서 삭제되거나 액세스 할 수없는 함수
  • 경우 T배열 형이며, 각 요소는 기본 초기화 ;
  • 그렇지 않으면 초기화가 수행되지 않습니다.

그러므로

  • new A단독 원인 A의 기본 생성자는 초기화하지 않는, 호출 할 수 있습니다 m. 불확실한 값. 에서 동일해야합니다 new B.
  • new A() [dcl.init] / 11 (C ++ 11의 경우 / 10)에 따라 해석됩니다.

    이니셜 라이저가 빈 괄호 세트 인 객체, 즉는 ()값이 초기화됩니다.

    이제 [dcl.init] / 8 (C ++ 11 †의 / 7)을 고려하십시오.

    유형의 객체 를 값 초기화T 한다는 것은 다음을 의미합니다.

    • 만약 T사용자가 제공되거나 삭제없이 기본 생성자 (12.1) 또는 기본 생성자와 하나 (아마도 CV 수식) 클래스 타입 (항 9)이며, 그 목적은, 기본적으로 초기화된다;
    • 경우 T사용자가 제공 또는 삭제 기본 생성자가없는 (아마도 이력서 자격) 클래스 유형이, 다음 개체가 0으로 초기화하고 기본 초기화에 대한 의미 론적 제약이 선택하고, T가 아닌 사소한 기본 생성자가있는 경우, 개체는 기본적으로 초기화됩니다.
    • 경우 T배열 형이고, 각 소자의 값으로 초기화이고;
    • 그렇지 않으면 개체가 0으로 초기화됩니다.

    따라서 new A()0으로 초기화 m됩니다. 그리고 이것은을 위해 동일해야 A하고 B.

  • new Cnew C()(C는 사용자가 제공하는 기본 생성자가!) - 기본값으로 초기화 마지막 인용문에서 첫 번째 글 머리 적용하기 때문에, 개체를 다시합니다. 그러나 이제 m두 경우 모두 생성자에서 초기화됩니다.


† 글쎄요,이 단락은 C ++ 11에서 약간 다른 표현을 가지고 있으며 결과를 바꾸지 않습니다 :

유형의 객체 를 값 초기화T 한다는 것은 다음을 의미합니다.

  • 경우 T사용자가 제공하는 생성자 (12.1)와 (아마도 이력서 자격) 클래스 유형 (9 절)이며, 다음의 기본 생성자 T 라고 (와 T가 액세스 가능한 기본 생성자가없는 경우 초기화가 잘못 형성된다);
  • 경우 T사용자가 제공하는 생성자가없는 (아마도 이력서 자격) 비 노조 클래스 형식 인 경우, 다음 개체는 제로가 초기화이며 T암시 적으로 선언 기본 생성자가 아닌 사소한이야 '그 생성자가 호출된다.
  • 경우 T배열 형이고, 각 소자의 값으로 초기화이고;
  • 그렇지 않으면 개체가 0으로 초기화됩니다.

@Gabriel 정말 아닙니다.
Columbo

아 그래서 당신은 주로 C ++ 14에 대해 이야기하고 있고 C ++ 11에 대한 참조는 괄호 안에 주어집니다
Gabriel

@Gabriel 맞습니다. 내 말은, C ++ 14가 최신 표준이기 때문에 전면에 있습니다.
Columbo

1
표준 전체에서 초기화 규칙을 추적하려는 시도에서 성가신 점은 게시 된 C ++ 14 및 C ++ 11 표준 사이에 많은 변경 (대부분? 전부?)이 DR을 통해 발생했으며 사실상 C ++ 11도 마찬가지라는 것입니다. . 그리고 post-C ++ 14 DR도 있습니다 ...
TC

@Columbo 나는 왜 struct A { int m; }; struct C { C() : m(){}; int m; };다른 결과를 생성하고 A의 m이 처음에 초기화되는 원인을 이해하지 못합니다 . 나는 내가 한 실험에 대한 전용 스레드를 열었으며 문제를 명확히하기 위해 귀하의 의견에 감사드립니다. 감사 stackoverflow.com/questions/45290121/...
darkThoughts

12

다음 답변은 C ++ 98 및 C ++ 03에 대한 참조 역할을하는 https://stackoverflow.com/a/620402/977038 답변을 확장합니다.

대답 인용

  1. C ++ 1998에는 0과 기본값의 두 가지 초기화 유형이 있습니다.
  2. C ++ 2003에서는 세 번째 유형의 초기화, 값 초기화가 추가되었습니다.

C ++ 11 (n3242 참조)

이니셜 라이저

8.5 이니셜 [dcl.init] 지정하는 변수 POD 비 POD 같이 어느 초기화 될 수있는 걸림쇠 또는 동등 - 초기화 하거나 할 수 보강-INIT-목록 또는 이니셜 절 aggregately이라 브레이스 또는-equal- 이니셜 라이저 또는 using (expression-list) . C ++ 11 이전에는 initializer-clause 가 C ++ 11에있는 것보다 더 제한 되었지만 (expression-list) 또는 initializer-clause 만 지원 되었습니다. C ++ 11에서 initializer-clause는 이제 할당 표현식 과는 별도로 braced-init-list를 지원합니다.C ++ 03에서와 마찬가지로. 다음 문법은 C ++ 11 표준에 새로 추가 된 부분이 굵게 표시된 새 지원 절을 요약 한 것입니다.

이니셜 라이저 :
    brace-or-equal-initializer
    (expression-list)
brace-or-equal-initializer :
    = initializer-clause
    braced-init-list
initializer-clause :
    assignment-expression
    braced-init-list
initializer-list :
    initializer-clause ... opt
    initializer-list, initializer-clause ... opt **
braced-init-list :
    {initializer-list, opt}
    {}

초기화

C ++ 03과 마찬가지로 C ++ 11은 여전히 ​​세 가지 형식의 초기화를 지원합니다.


노트

굵게 강조 표시된 부분은 C ++ 11에서 추가되었으며 취소 된 부분은 C ++ 11에서 제거되었습니다.

  1. 이니셜 라이저 유형 : 8.5.5 [dcl.init] _zero-initialize_

다음과 같은 경우 수행

  • 정적 또는 스레드 저장 기간이있는 개체는 0으로 초기화됩니다.
  • 이니셜 라이저가 배열 요소보다 적 으면 명시 적으로 초기화되지 않은 각 요소는 0으로 초기화됩니다.
  • value-initialize 중에 T가 사용자가 제공 한 생성자가없는 (가능하게는 cv-qualified) non-union 클래스 유형이면 객체는 0으로 초기화됩니다.

개체 또는 T 형식 참조를 0으로 초기화한다는 것은 다음을 의미합니다.

  • T가 스칼라 유형 (3.9)이면 객체는 정수 상수 표현식으로 간주 되어 T로 변환 된 값 0 (영)으로 설정됩니다 .
  • T가 (가능하게는 cv-qualified) 비 결합 클래스 유형이면, 각 비 정적 데이터 멤버와 각 기본 클래스 하위 객체는 0으로 초기화 되고 패딩은 0 비트로 초기화됩니다.
  • T가 (가능하게는 cv-qualified) 공용체 유형 인 경우 객체의 첫 번째 비 정적 명명 된 데이터 멤버는 0으로 초기화 되고 패딩은 0 비트로 초기화됩니다.
  • T가 배열 유형이면 각 요소는 0으로 초기화됩니다.
  • T가 참조 유형이면 초기화가 수행되지 않습니다.

2. 이니셜 라이저 유형 : 8.5.6 [dcl.init] _default-initialize_

다음과 같은 경우 수행

  • new-initializer가 생략되면 개체는 기본적으로 초기화됩니다. 초기화가 수행되지 않으면 개체의 값이 결정되지 않습니다.
  • 개체에 대해 초기화 프로그램이 지정되지 않은 경우 개체는 기본적으로 초기화됩니다. 단, 정적 또는 스레드 저장 기간이있는 개체는 예외입니다.
  • 기본 클래스 또는 비 정적 데이터 멤버가 생성자 이니셜 라이저 목록에 언급되지 않고 해당 생성자가 호출되는 경우.

T 유형의 개체를 기본 초기화한다는 것은 다음을 의미합니다.

  • T가 (아마도 cv-qualified) non-POD 클래스 유형 인 경우 (Clause 9), T에 대한 기본 생성자가 호출됩니다 (T에 액세스 할 수있는 기본 생성자가 없으면 초기화 형식이 잘못됨).
  • T가 배열 유형이면 각 요소는 기본값으로 초기화됩니다.
  • 그렇지 않으면 초기화가 수행되지 않습니다.

참고 C ++ 11까지는 이니셜 라이저를 사용하지 않을 때 자동 저장 기간이있는 비 POD 클래스 유형 만 기본값으로 초기화 된 것으로 간주되었습니다.


3. 이니셜 라이저 유형 : 8.5.7 [dcl.init] _value-initialize_

  1. 이니셜 라이저가 빈 괄호 세트 (예 : () 또는 중괄호 {}) 인 객체 (이름없는 임시, 명명 된 변수, 동적 저장 기간 또는 비 정적 데이터 멤버)

T 유형의 객체를 값 초기화한다는 것은 다음을 의미합니다.

  • T가있는 경우 (아마도 이력서 자격) 사용자가 제공하는 생성자 (12.1)와 클래스 유형 (9 절), 그 다음 T의 기본 생성자가 호출됩니다 (그리고 T가 액세스 가능한 기본 생성자가없는 경우 초기화가 잘못 형성된다) ;
  • T가 사용자가 제공 한 생성자가없는 (가능하게는 cv-qualified) non-union 클래스 유형 인 경우, T의 모든 비 정적 데이터 멤버 및 기본 클래스 구성 요소는 값이 초기화됩니다. 객체는 0으로 초기화되고 T의 암시 적으로 선언 된 기본 생성자가 중요하지 않은 경우 해당 생성자가 호출됩니다.
  • T가 배열 유형이면 각 요소는 값으로 초기화됩니다.
  • 그렇지 않으면 개체가 0으로 초기화됩니다.

요약하자면

참고 표준의 관련 견적은 굵게 강조 표시됩니다.

  • new A : default-initializes (A :: m은 초기화되지 않음)
  • new A () : 값 초기화 후보에 사용자가 제공하거나 삭제 한 기본 생성자가 없으므로 A를 0으로 초기화합니다. T가 사용자가 제공 한 생성자가없는 (가능하게는 cv-qualified) non-union 클래스 유형 인 경우 객체는 0으로 초기화되고 T의 암시 적으로 선언 된 기본 생성자가 중요하지 않은 경우 해당 생성자가 호출됩니다.
  • new B : default-initializes (B :: m은 초기화되지 않음)
  • new B () : 모든 필드를 0으로 초기화하는 B 값을 초기화합니다. T가 사용자가 제공 한 생성자 (12.1)가있는 (아마도 cv-qualified) 클래스 유형 (Clause 9)이면 T의 기본 생성자가 호출됩니다.
  • new C : 기본 ctor를 호출하는 C를 기본 초기화합니다. T가 (아마도 cv-qualified) 클래스 유형이면 (Clause 9) T의 기본 생성자가 호출됩니다 .
  • new C () : 기본 ctor를 호출하는 C 값을 초기화합니다. T가 사용자가 제공 한 생성자 (12.1)가있는 (아마도 cv-qualified) 클래스 유형 (Clause 9)이면 T에 대한 기본 생성자가 호출됩니다. 또한 이니셜 라이저가 빈 괄호 집합 인 () 인 객체는 값이 초기화됩니다.

0

나는 C ++ 11에서 C ++ 14의 질문에 언급 된 모든 것이 적어도 컴파일러 구현에 따라 정확하다는 것을 확인할 수 있습니다.

이를 확인하기 위해 테스트 스위트에 다음 코드를 추가했습니다 . 나는 시험 -std=c++11 -O3GCC 7.4.0, GCC 5.4.0, 연타 10.0.1 및 VS 2017 년, 그리고 패스 아래의 모든 테스트합니다.

#include <gtest/gtest.h>
#include <memory>

struct A { int m;                    };
struct B { int m;            ~B(){}; };
struct C { int m; C():m(){}; ~C(){}; };
struct D { int m; D(){};             };
struct E { int m; E() = default;     };
struct F { int m; F();               }; F::F() = default;

// We use this macro to fill stack memory with something else than 0.
// Subsequent calls to EXPECT_NE(a.m, 0) are undefined behavior in theory, but
// pass in practice, and help illustrate that `a.m` is indeed not initialized
// to zero. Note that we initially tried the more aggressive test
// EXPECT_EQ(a.m, 42), but it didn't pass on all compilers (a.m wasn't equal to
// 42, but was still equal to some garbage value, not zero).
//
#define FILL { int m = 42; EXPECT_EQ(m, 42); }

// We use this macro to fill heap memory with something else than 0, before
// doing a placement new at that same exact location. Subsequent calls to
// EXPECT_EQ(a->m, 42) are undefined behavior in theory, but pass in practice,
// and help illustrate that `a->m` is indeed not initialized to zero.
//
#define FILLH(b) std::unique_ptr<int> bp(new int(42)); int* b = bp.get(); EXPECT_EQ(*b, 42)

TEST(TestZero, StackDefaultInitialization)
{
    { FILL; A a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; B a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; C a; EXPECT_EQ(a.m, 0); }
    { FILL; D a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a; EXPECT_NE(a.m, 0); } // UB!
    { FILL; F a; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackValueInitialization)
{
    { FILL; A a = A(); EXPECT_EQ(a.m, 0); }
    { FILL; B a = B(); EXPECT_EQ(a.m, 0); }
    { FILL; C a = C(); EXPECT_EQ(a.m, 0); }
    { FILL; D a = D(); EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a = E(); EXPECT_EQ(a.m, 0); }
    { FILL; F a = F(); EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, StackListInitialization)
{
    { FILL; A a{}; EXPECT_EQ(a.m, 0); }
    { FILL; B a{}; EXPECT_EQ(a.m, 0); }
    { FILL; C a{}; EXPECT_EQ(a.m, 0); }
    { FILL; D a{}; EXPECT_NE(a.m, 0); } // UB!
    { FILL; E a{}; EXPECT_EQ(a.m, 0); }
    { FILL; F a{}; EXPECT_NE(a.m, 0); } // UB!
}

TEST(TestZero, HeapDefaultInitialization)
{
    { FILLH(b); A* a = new (b) A; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); B* a = new (b) B; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); C* a = new (b) C; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); F* a = new (b) F; EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapValueInitialization)
{
    { FILLH(b); A* a = new (b) A(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D(); EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E(); EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F(); EXPECT_EQ(a->m, 42); } // ~UB
}

TEST(TestZero, HeapListInitialization)
{
    { FILLH(b); A* a = new (b) A{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); B* a = new (b) B{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); C* a = new (b) C{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); D* a = new (b) D{}; EXPECT_EQ(a->m, 42); } // ~UB
    { FILLH(b); E* a = new (b) E{}; EXPECT_EQ(a->m, 0);  }
    { FILLH(b); F* a = new (b) F{}; EXPECT_EQ(a->m, 42); } // ~UB
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

UB!언급 된 장소 는 정의되지 않은 동작이며 실제 동작은 여러 요인에 따라 달라질 a.m수 있습니다 ( 42, 0 또는 기타 쓰레기와 같을 수 있음). ~UB언급 된 장소 도 이론상 정의되지 않은 동작이지만 실제로는 새로운 배치를 사용하기 때문에 a->m42가 아닌 다른 것과 같을 가능성 은 거의 없습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.