초기화되지 않은 멤버로 구조체 복사


29

멤버가 초기화되지 않은 일부 구조체를 복사하는 것이 유효합니까?

나는 그것이 정의되지 않은 행동이라고 생각하지만, 그렇다면 초기화되지 않은 멤버를 구조체에 남겨 두는 것은 매우 위험합니다. 그래서 표준에 허용되는 것이 있는지 궁금합니다.

예를 들어, 이것이 유효합니까?

struct Data {
  int a, b;
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

얼마 전에 비슷한 질문을 보았지만 찾을 수 없습니다. 이 질문 은 이 질문 과 관련 있습니다.
1201 프로그램 알람

답변:


23

예, 초기화되지 않은 멤버가 서명되지 않은 좁은 문자 유형이 아니거나 std::byte암시 적으로 정의 된 복사 생성자를 사용하여이 결정되지 않은 값을 포함하는 구조체를 복사하는 것은 기술적으로 정의되지 않은 동작입니다. 의 [dcl.init] / 12 .

암시 적으로 생성 된 복사 생성자가 union직접 초기화에 의한 것처럼 각 멤버를 개별적으로 복사하도록 정의 된 s를 제외하고 여기에 적용됩니다 . [class.copy.ctor] / 4를 참조하십시오 .

이것은 또한 유효한 CWG 문제 2264 의 주제입니다 .

실제로 실제로는 아무런 문제가 없을 것이라고 생각합니다.

100 % 확실 하게하려면 멤버가 값을 결정할 수없는 경우에도 std::memcpy유형을 간단하게 복사 할 수있는 경우 항상를 사용하여 올바르게 정의 된 동작을 갖습니다.


이러한 문제를 제외하고는 클래스가 간단한 기본 생성자 를 가질 필요가 없다고 가정 할 때 항상 구성시 지정된 값으로 클래스 멤버를 올바르게 초기화해야합니다 . 기본 멤버 이니셜 라이저 구문을 사용하여 멤버를 값 초기화하는 등의 작업을 쉽게 수행 할 수 있습니다.

struct Data {
  int a{}, b{};
};

int main() {
  Data data;
  data.a = 5;
  Data data2 = data;
}

글쎄 .. 그 구조체는 POD (일반 오래된 데이터)가 아닌가? 그것은 멤버가 기본값으로 초기화된다는 것을 의미합니까? 의심의 여지가 있습니다
Kevin Kouketsu

이 경우에 얕은 사본이 아닙니까? 복사되지 않은 구조체에서 초기화되지 않은 멤버에 액세스하지 않으면 무엇이 잘못 될 수 있습니까?
TruthSeeker

@KevinKouketsu 나는 사소한 / POD 유형이 필요한 경우에 대한 조건을 추가했습니다.
호두

@TruthSeeker 표준은 정의되지 않은 동작이라고 말합니다. (멤버가 아닌) 변수에 대해 일반적으로 정의되지 않은 동작이 발생하는 이유는 AndreySemashev의 답변에 설명되어 있습니다. 기본적으로 초기화되지 않은 메모리로 트랩 표현을 지원합니다. 이것이 여부 위한 구조체의 암시 적 복사 건설에 적용 할 연결 CWG 문제의 질문입니다.
호두

@TruthSeeker 암시 적 복사 생성자는 직접 초기화에 의한 것처럼 각 멤버를 개별적으로 복사하도록 정의됩니다. memcpy사소한 복사 가능한 유형의 경우 에도 객체 표현을 by처럼 복사하도록 정의되지 않았습니다 . 유일한 예외는 암시 적 복사 생성자가 객체 표현을 마치 by로 복사하는 공용체 memcpy입니다.
호두

11

일반적으로 초기화되지 않은 데이터를 복사하는 것은 정의되지 않은 동작입니다. 그 데이터는 트래핑 상태 일 수 있기 때문입니다. 페이지 인용 :

객체 표현이 객체 유형의 값을 나타내지 않으면이를 트랩 표현이라고합니다. 문자 유형의 lvalue 표현식을 통해 읽는 것 이외의 방법으로 트랩 표현에 액세스하는 것은 정의되지 않은 동작입니다.

부동 소수점 유형의 경우 신호 NaN이 가능하며 일부 플랫폼에서는 정수에 트랩 표현 이있을 수 있습니다 .

그러나 사소하게 복사 가능한 유형 memcpy의 경우 객체의 원시 표현을 복사하는 데 사용할 수 있습니다. 객체의 값이 해석되지 않고 대신 객체 표현의 원시 바이트 시퀀스가 ​​복사되므로 안전합니다.


모든 비트 패턴이 유효한 값을 나타내는 유형의 데이터는 어떻습니까 (예 :를 포함하는 64 바이트 구조체 unsigned char[64])? 구조체의 바이트를 지정되지 않은 값을 갖는 것으로 취급하면 최적화를 불필요하게 방해 할 수 있지만 프로그래머가 불필요한 값으로 배열을 수동으로 채우도록 요구하면 효율성이 훨씬 더 떨어집니다.
supercat

데이터를 초기화하는 것은 쓸모가 없으며 트랩 표현으로 인해 발생했는지 또는 나중에 초기화되지 않은 데이터를 사용하여 발생하는지에 관계없이 UB를 방지합니다. 64 바이트 (1 개 또는 2 개의 캐시 라인)를 제로화하는 것은 생각보다 비싸지 않습니다. 고가의 구조가 큰 경우 복사하기 전에 두 번 생각해야합니다. 그리고 나는 당신이 언젠가 그것들을 초기화해야한다고 확신합니다.
Andrey Semashev

프로그램 동작에 영향을 줄 수없는 머신 코드 작업은 쓸모가 없습니다. 표준에 의해 UB로 특징 지어진 어떤 행동도 모든 비용으로 피해야한다는 개념보다는 [C 표준위원회의 말에 따르면] UB는 "적합한 언어 확장이 가능한 영역을 식별한다"는 것이 비교적 최근의 개념이라고 말하고있다. C ++ 표준에 대해 공개 된 이론적 근거를 보지는 못했지만, C ++ 프로그램이 준수 또는 부적합으로 분류하는 것을 거부함으로써 C ++ 프로그램의 "허용"에 대한 관할권을 명시 적으로 포기함으로써 유사한 확장을 허용합니다.
supercat

-1

설명 된 것과 같은 일부 경우에 C ++ 표준을 사용하면 컴파일러가 동작을 예측할 필요없이 고객이 가장 유용하다고 생각하는 방식으로 구문을 처리 할 수 ​​있습니다. 다시 말해, 이러한 구성은 "정의되지 않은 동작"을 불러옵니다. 그러나 C ++ 표준이 잘 ​​구성된 프로그램이 "허용"하는 것에 대한 관할권을 명시 적으로 포기하기 때문에 그러한 구성이 "금지"되어야 함을 의미하지는 않습니다. C ++ 표준에 대해 게시 된 Rational 문서를 알지 못하지만 C89와 같이 정의되지 않은 동작을 설명한다는 사실은 의도 된 의미가 비슷하다는 것을 시사합니다. 진단합니다.

무언가를 처리하는 가장 효율적인 방법은 다운 스트림 코드가 신경 쓰지 않는 구조 부분을 작성하는 반면, 다운 스트림 코드는 신경 쓰지 않는 부분을 생략하는 상황이 많이 있습니다. 프로그램이 신경 쓰지 않는 것을 포함하여 구조의 모든 멤버를 초기화하도록 요구하면 효율성을 불필요하게 방해 할 수 있습니다.

또한 초기화되지 않은 데이터가 비 결정적 방식으로 동작하는 것이 가장 효율적인 상황이 있습니다. 예를 들면 다음과 같습니다.

struct q { unsigned char dat[256]; } x,y;

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
    temp.dat[arr[i]] = i;
  x=temp;
  y=temp;
}

다운 스트림 코드의 어떤 요소의 값을 걱정하지 않을 경우 x.dat또는 y.dat그 인덱스에 나열되지 않은 arr, 코드가에 최적화 될 수 있습니다

void test(unsigned char *arr, int n)
{
  q temp;
  for (int i=0; i<n; i++)
  {
    int it = arr[i];
    x.dat[index] = i;
    y.dat[index] = i;
  }
}

프로그래머가 temp.dat복사하기 전에 다운 스트림이 신경 쓰지 않는 요소를 포함하여 의 모든 요소를 ​​명시 적으로 작성해야하는 경우 효율성의 향상은 불가능 합니다.

반면에 데이터 유출 가능성을 피하는 것이 중요한 일부 응용 프로그램이 있습니다. 이러한 응용 프로그램에서는 다운 스트림 코드가 확인되는지 여부에 관계없이 초기화되지 않은 저장소를 복사하려는 시도를 포착하도록 설계된 코드 버전을 사용하는 것이 유용 할 수 있습니다. 누수 될 수있는 내용은 0이되거나 기밀이 아닌 데이터로 덮어 씁니다.

내가 알 수 있듯이 C ++ 표준은 이러한 행동 중 하나가 명령을 정당화하는 데 다른 것보다 충분히 유용하다고 말하지 않습니다. 아이러니하게도, 이러한 사양 부족은 최적화를 용이하게하기위한 것이지만, 프로그래머가 어떤 종류의 약한 행동 보장을 이용할 수 없다면 최적화가 무효화됩니다.


-2

의 모든 멤버는 Data기본 유형 이므로의 모든 멤버의 data2정확한 "비트 단위 복사"를 data받습니다. 따라서의 값은의 값과 data2.b정확히 같습니다 data.b. 그러나 data.b명시 적으로 초기화하지 않았으므로 정확한 값을 예측할 수 없습니다. 에 할당 된 메모리 영역의 바이트 값에 따라 다릅니다 data.


표준을 참조하여이를 지원할 수 있습니까? @walnut에서 제공하는 링크는 이것이 정의되지 않은 동작임을 암시합니다. 표준에 POD에 대한 예외가 있습니까?
Tomek Czajka

다음은 표준에 링크되어 있지는 않지만 다음과 같습니다. en.cppreference.com/w/cpp/language/… "TriviallyCopyable 객체는 객체 표현을 수동으로 복사하여 복사 할 수 있습니다 (예 : std :: memmove) C와 호환되는 모든 데이터 유형 언어 (POD 유형)는 간단하게 복사 할 수 있습니다. "
ivan.ukr

이 경우 "정의되지 않은 동작"은 초기화되지 않은 멤버 변수의 값을 예측할 수 없다는 것입니다. 그러나 코드는 성공적으로 컴파일되고 실행됩니다.
ivan.ukr

1
인용 한 조각은 memmove의 동작에 대해 이야기하지만 내 코드에서는 memmove가 아닌 복사 생성자를 사용하기 때문에 실제로 관련이 없습니다. 다른 답변은 복사 생성자를 사용하면 정의되지 않은 동작이 발생한다는 것을 의미합니다. "정의되지 않은 행동"이라는 용어를 오해하고 있다고 생각합니다. 그것은 언어가 전혀 보증을 제공하지 않음을 의미합니다. 예를 들어 프로그램이 무작위로 데이터를 충돌 시키거나 손상시킬 수 있습니다. 그것은 단지 어떤 가치가 예측할 수 없다는 것을 의미하는 것이 아니며, 그것은 지정되지 않은 행동 일 것입니다.
Tomek Czajka

@ ivan.ukr C ++ 표준은 암시 적 복사 / 이동 생성자가 직접 초기화에 의한 것처럼 멤버 단위로 작동하도록 지정합니다. 내 답변의 링크를 참조하십시오. 따라서 복사 구성은 " "비트 단위 복사 " "를 만들지 않습니다 . 암시 적 복사 생성자 manual과 같은 방식으로 객체 표현을 복사하도록 지정된 공용체 유형에 대해서만 정확합니다 . 이 중 어느 것도 or 사용을 방해하지 않습니다 . 암시 적 복사 생성자를 사용하지 않습니다. std::memcpystd::memcpystd::memmove
호두
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.