중복 된 빈 기본 저장소가 vtable 포인터와 겹치지 않는 이유는 무엇입니까?


11

이 예제를 고려하십시오.

#include <iostream>

int main()
{
    struct A {};
    struct B : A {};
    struct C : A, B {};

    std::cout << sizeof(A) << '\n'; // 1
    std::cout << sizeof(B) << '\n'; // 1
    std::cout << sizeof(C) << '\n'; // 2, because of a duplicate base

    struct E : A {virtual ~E() {}};
    struct F : A, B {virtual ~F() {}};

    std::cout << sizeof(E) << '\n'; // 8, the base overlaps the vtable pointer
    std::cout << sizeof(F) << '\n'; // 16, but why?
}

(갓 볼트에서 실행)

여기 struct E에서 비어있는 기본 클래스 (1 바이트)는 예상대로 vtable 포인터와 동일한 스토리지를 사용합니다.

그러나 struct F빈베이스가 중복 된에 대해서는 이런 일이 발생하지 않습니다. 무엇이 원인입니까?

GCC, Clang 및 MSVC에서 동일한 결과를 얻습니다. 위의 결과는 x64에 대한 것 sizeof(void *) == 8입니다.


흥미롭게도 struct G : A, B {void *ptr;};GCC와 Clang의 경우 EBO를 수행하지만 (크기는 8) MSVC는 수행하지 않습니다 (크기는 16).


3
이상하게도, 상속에 의해 C(어떤이 상속 A, B) 당신은 폼 상속과는 다른 결과를 얻을 수 AB직접
기욤 Racicot의

1
나는 이것을 연구하는 것을 즐겼다. 질문과 링크에 감사드립니다. 나는 대답이 확실하지 않으므로 간단히 언급 할 것입니다. 이것은 C과 에서 파생 된 애매함에서 비롯된 것일 수 F있습니까? 결국, 2 * sizeof(void*) == 16x86_64에서 말했듯이. 컴파일러는 Story Teller가 말했듯이 완전히 최적화 할 수 없으므로 그렇게 최적화 할 수 없습니다.
앤드류 팔랑가

2
gcc와 clang은 모두 itanium ABI를 따르기 때문에 동일한 결과를 얻는 것이 일반적입니다. 그리고 이것이 내가 생각하는 경우 ABI를 정의 할 때 레이아웃 알고리즘이 너무 비싸 질 우려가 있었기 때문에 바로 가기 (일명 비관 화)가 필요했습니다.
Marc Glisse

2
@RianQuinn 복제 기준이 구조를 유효하게 만들지는 않습니다.
HolyBlackCat

1
서로 다른 "경로"를 통해 동일한 클래스에서 여러 번 상속되는 @RianQuinn은 C ++에서 완벽하게 유효합니다. 다이아몬드 구조를 만들려면 (예 : 기본 클래스를 한 번만 사용하려면) 가상 상속을 사용해야합니다. 그러나 다이아몬드를 원하지 않고 기본 클래스가 중복 되어도 문제가되지 않으면 언어에도 문제가되지 않습니다. OP의 코드 A는를 통해 상속 된 두 번째 에 B액세스 할 수 없다는 경고 만 표시합니다 . 괜찮습니다. 예제와 같이 실제로 액세스하려고 시도하는 경우에만 오류가 발생합니다.
sebrockm

답변:


4

컴파일러가 구조체 A 다음에 1 바이트 패딩을 추가하기 때문에

F {vptr (8) + 0 멤버 A + 1 패딩 (A가 비어 있기 때문에) +0 from b} = 9 컴파일러는 구조체의 스토리지를 정렬하기 위해 7 바이트 패딩을 추가합니다.

E {vptr (8) + A의 멤버 0 명 = 8 패딩 필요 없음

Microsoft에서

모든 데이터 객체에는 정렬 요구 사항이 있습니다. 구조의 경우 요구 사항이 가장 큰 멤버입니다. 오프셋 % alignment-requirement == 0이되도록 모든 객체에 오프셋이 할당됩니다.

https://docs.microsoft.com/en-us/cpp/c-language/storage-and-alignment-of-structures?view=vs-2019

편집하다:

여기 내 데모가 있습니다 :

int main()
{
    C c;
    A* a = &c;
    B* b = &c;

    std::cout << sizeof(A) << " " << a << '\n'; 
    std::cout << sizeof(B) << " " << b << '\n'; 
    std::cout << sizeof(C) << " " << &c << '\n'; 

    E e;
    a = &e;
    std::cout << sizeof(E) <<" " << &e << " " << a << '\n'; 

    F f;
    a = &f;
    b = &f;
    std::cout << sizeof(F) << " " << &f << " " << a << " " << b << '\n';

}

산출:

1 0000007A45B7FBB4
1 0000007A45B7FBB5
1 0000007A45B7FBB4
8 0000007A45B7FC18 0000007A45B7FC20
16 0000007A45B7FC38 0000007A45B7FC40 0000007A45B7FC41

보시다시피 a와 b는 서로 겹치지 않으며 여러 상속에서 vptr을 사용하면 각각 고유 한 포인터 값이 있습니다.

VC2019 x64 빌드로 컴파일 된 노트


이것이 작동하는 방식이라고 생각하지 않습니다. 비록 A여전히 (타 물체와 공유 할 수 있습니다) 1 바이트를 차지, 회원이 없습니다. 에서가 E, Avptr에서 뒤에 위치하지 않습니다; vptr의 첫 바이트와 겹칩니다. (여기 데모가 있습니다 ; A접근 가능 하도록 코드를 약간 수정했습니다 .). 의 첫 번째 항목 A에서도 마찬가지입니다 F. vptr 위에 A(및 B)을 배치 할 수 있기 때문에 왜 발생하지 않는지 잘 모르겠습니다 B.
HolyBlackCat

@HolyBlackCat 그러나 이것이 일어난 일입니다. 테스트 코드 확인
Ahmed Anter

Aha, 따라서 MSVC는 GCC / Clang과 다르게 작동합니다. Avptr 위에 올려 놓을만큼 똑똑하지 않습니다 . MSVC에서 출력이 16 인 이유를 설명 할 수는 있지만 GCC 및 Clang에서 무슨 일이 일어나고 있는지 잘 모르겠습니다.
HolyBlackCat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.