Yinelenen boş bir taban için depolama neden vtable işaretçisiyle çakışmıyor?


11

Bu örneği düşünün:

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

(godbolt üzerinde koş)

Burada struct E, boş temel sınıf (1 bayt büyük) için beklendiği gibi vtable işaretçisiyle aynı depolamayı kullandığını görebilirsiniz.

Ancak struct F, yinelenen boş bir tabana sahip olduğu için , bu gerçekleşmez. Buna ne sebep olur?

Aynı sonucu GCC, Clang ve MSVC'de de alıyorum. Yukarıdaki sonuçlar x64 içindir sizeof(void *) == 8.


İlginçtir, struct G : A, B {void *ptr;};GCC ve Clang için EBO (boyut 8'dir), ancak MSVC yapmaz (boyut 16'dır).


3
Garip, devralan tarafından C(hangi miras A, B) formu devralan farklı bir sonuç elde Ave Bdoğrudan
Guillaume Racicot

1
Bunu araştırmaktan zevk aldım. Soru ve bağlantı için teşekkürler. Bir cevabım olduğundan emin değilim ve bu yüzden yorum yapacağım. Bu derivasyon tanıtıldı belirsizlik kaynaklandığına olabilir Cve F? Sonuçta, 2 * sizeof(void*) == 16dediğin gibi x86_64 üzerinde. Derleyici tam olarak optimize edemez (Story Teller'ın dediği gibi) ve öyle değildir.
Andrew Falanga

2
İkisi de itanium ABI'yi takip ettiğinden, gcc ve clang'da aynı sonucu almanız normaldir. Ve bence durum buysa, ABI'yi tanımlarken, düzen algoritmasının çok pahalı olabileceğinden korktular, bu yüzden bazı kısayollar aldılar (kötümserlikler).
Marc Glisse

2
@RianQuinn Yinelenen bir taban, bir yapıyı geçersiz yapmaz.
HolyBlackCat

1
@RianQuinn, aynı sınıftan farklı "yollar" yoluyla birçok kez miras alınması C ++ 'da mükemmel şekilde geçerlidir. Eğer bir elmas yapısı oluşturmak istiyorsanız, yani temel sınıfı sadece bir kez almak istiyorsanız, sanal kalıtım kullanmanız gerekir. Ama bir elmas istemiyorsanız ve yinelenen bir taban sınıfına sahip olmak sizin için sorun değilse, bu da dil için bir sorun değildir. OP kodu yalnızca Aüzerinden devralınan ikinciye Berişilemediğini söyleyerek bir uyarı oluşturur . Bu iyi. Yalnızca örneğinizde olduğu gibi erişmeye çalışırsanız bir hata alırsınız.
sebrockm

Yanıtlar:


4

Derleyici, A yapısından sonra bir bayt dolgu eklediğinden

F {vptr (8) + A + 1 dolgudan 0 üye (A boş olduğu için) b} = 9'dan +0 sonra derleyici, yapının depolanmasını hizalamak için 7 bayt dolgu ekler;

E {vptr (8) + A için 0 üye} = 8 Dolgu gerekmez

Microsoft'tan

Her veri nesnesinin bir hizalama gereksinimi vardır. Yapılar için gereklilik üyelerin en büyüğüdür. Her nesneye bir ofset ayrılır, böylece ofset% hizalama-gereksinimi == 0

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

DÜZENLE:

İşte benim demo:

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

}

çıktı:

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

a & b'nin asla birbiriyle örtüşmediğini ve birden fazla kalıtımda vptr ile her birinin kendi işaretçi değeri olduğunu görebilirsiniz

VC2019 x64 derlemesi tarafından derlenen not


Ben böyle çalıştığını sanmıyorum. AHiçbir üyesi olmasa da , hala 1 byte yer kaplar (başka bir nesne ile paylaşılabilir). In E, Avptr'den sonra bulunmaz; vptr'nin ilk baytıyla çakışır. (İşte bir demo ; Aerişilebilir olması için kodu biraz değiştirdim .). Aynı şey ilk Agelen için de geçerli F. Yana A(ve B) vptr üstüne yerleştirilebilir bunun için olmaz neden emin değilim B.
HolyBlackCat

@HolyBlackCat ama bu oldu test kodunu kontrol edin
Ahmed Anter

Aha, bu yüzden MSVC burada GCC / Clang'dan farklı davranıyor; Avptr'nin üstüne koymak için yeterince akıllı değil . MSVC'de çıktının neden 16 olduğunu açıklayabilir, ancak GCC & Clang ile neler olup bittiğinden emin değilim.
HolyBlackCat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.