Mengapa penyimpanan untuk duplikat basis kosong tumpang tindih dengan pointer vtable?


11

Pertimbangkan contoh ini:

#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?
}

(Jalankan godbolt)

Di sini Anda dapat melihat bahwa untuk struct Ekelas dasar kosong (yang berukuran 1 byte) menggunakan penyimpanan yang sama dengan pointer vtable, seperti yang diharapkan.

Tetapi untuk struct F, yang memiliki basis kosong duplikat, ini tidak terjadi. Apa yang menyebabkan ini?

Saya mendapatkan hasil yang sama pada GCC, Dentang, dan MSVC. Hasil di atas adalah untuk x64, jadi sizeof(void *) == 8.


Menariknya, untuk struct G : A, B {void *ptr;};GCC dan Dentang lakukan melakukan EBO (ukurannya 8), tetapi MSVC tidak (ukurannya 16).


3
Anehnya, dengan mewarisi dari C(yang mewarisi dari A, B) Anda mendapatkan hasil yang berbeda dari bentuk warisan Adan Blangsung
Guillaume Racicot

1
Saya senang meneliti yang ini. Terima kasih atas pertanyaan dan tautannya. Saya tidak yakin saya punya jawaban dan hanya akan berkomentar. Mungkinkah ini timbul dari ambiguitas yang diperkenalkan dari derivasi Cdan F? Bagaimanapun, 2 * sizeof(void*) == 16pada x86_64 seperti yang Anda katakan. Kompiler tidak dapat sepenuhnya mengoptimalkan (seperti yang dikatakan Story Teller) dan begitu juga tidak.
Andrew Falanga

2
Adalah normal bahwa Anda mendapatkan hasil yang sama pada gcc dan dentang, karena keduanya mengikuti ABI itanium. Dan jika ini masalahnya saya pikir, ketika mendefinisikan ABI, mereka takut algoritma tata letak mungkin menjadi terlalu mahal, jadi mereka mengambil beberapa jalan pintas (alias pesimisasi).
Marc Glisse

2
@RianQuinn Basis duplikat tidak membuat struktur tidak valid.
HolyBlackCat

1
@RianQuinn mewarisi dari kelas yang sama beberapa kali melalui "jalur" yang berbeda sangat valid di C ++. Jika Anda ingin membuat struktur berlian, yaitu hanya memiliki kelas dasar sekali, Anda harus menggunakan warisan virtual. Tetapi jika Anda tidak menginginkan berlian dan memiliki kelas dasar duplikat bukan masalah bagi Anda, maka ini juga tidak masalah untuk bahasa. Kode OP hanya menghasilkan peringatan, mengatakan bahwa yang kedua A, yang diwarisi melalui B, tidak dapat diakses. Ini baik saja. Hanya jika Anda benar-benar mencoba mengaksesnya, seperti dalam contoh Anda, Anda mendapatkan kesalahan.
sebrockm

Jawaban:


4

Karena kompiler menambahkan padding satu byte setelah struct A

F {vptr (8) + 0 anggota dari A + 1 padding (karena A kosong) +0 dari b} = 9 maka kompiler menambahkan 7 byte padding untuk menyelaraskan penyimpanan struct;

E {vptr (8) + 0 anggota untuk A} = 8 Tidak diperlukan bantalan

dari Microsoft

Setiap objek data memiliki persyaratan pelurusan. Untuk struktur, persyaratannya adalah yang terbesar dari anggotanya. Setiap objek dialokasikan offset sehingga offset% alignment-requirement == 0

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

EDIT:

ini demo saya:

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';

}

keluaran:

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

seperti yang Anda lihat a & b tidak pernah tumpang tindih satu sama lain dan dengan vptr pada banyak pewarisan masing-masing memiliki nilai penunjuk sendiri

catatan dikompilasi oleh VC2019 x64 build


Saya rasa ini bukan cara kerjanya. Meskipun Atidak memiliki anggota, ia masih menempati 1 byte (yang dapat dibagikan dengan objek lain). In E, Atidak terletak setelah vptr; itu tumpang tindih dengan byte pertama dari vptr. (Ini demo ; Saya telah sedikit memodifikasi kode agar Adapat diakses.). Hal yang sama terjadi pada yang pertama Adi F. Karena A(dan B) dapat ditempatkan di atas vptr, saya tidak yakin mengapa itu tidak terjadi B.
HolyBlackCat

@HolyBlackCat tetapi inilah yang terjadi dengan kode uji periksa
Ahmed Anter

Aha, jadi MSVC berperilaku berbeda dari GCC / Dentang di sini; itu tidak cukup pintar untuk diletakkan Adi atas vptr. Itu bisa menjelaskan mengapa outputnya 16 pada MSVC, tapi saya tidak yakin apa yang terjadi dengan GCC & Dentang.
HolyBlackCat
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.