Perché l'archiviazione per una base vuota duplicata non si sovrappone a un puntatore vtable?


11

Considera questo esempio:

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

(corri su godbolt)

Qui puoi vedere che per struct Ela classe base vuota (che è grande 1 byte) usa la stessa memoria del puntatore vtable, come previsto.

Ma per struct F, che ha una base vuota duplicata, questo non accade. Cosa causa questo?

Ottengo lo stesso risultato su GCC, Clang e MSVC. I risultati sopra riportati sono per x64, quindi sizeof(void *) == 8.


È interessante notare che struct G : A, B {void *ptr;};GCC e Clang eseguono EBO (la dimensione è 8), ma MSVC no (la dimensione è 16).


3
Stranamente, ereditando C( da cui ereditano ) A, Bsi ottengono risultati diversi rispetto all'ereditarietà della forma Ae Bdirettamente
Guillaume Racicot

1
Mi è piaciuto ricercare questo. Grazie per la domanda e il link. Non sono sicuro di avere una risposta e quindi semplicemente commenterò. Potrebbe essere che ciò deriva dall'ambiguità introdotta dalla derivazione di Ce F? Dopotutto, 2 * sizeof(void*) == 16su x86_64 come hai detto. Il compilatore non può ottimizzare completamente (come ha detto Story Teller) e così non lo è.
Andrew Falanga,

2
È normale ottenere lo stesso risultato su gcc e clang, poiché entrambi seguono l'ABI itanium. E se questo è il caso, penso che, nel definire l'ABI, temessero che l'algoritmo di layout potesse diventare troppo costoso, quindi hanno preso delle scorciatoie (ovvero pessimizzazioni).
Marc Glisse,

2
@RianQuinn Una base duplicata non rende invalida una struttura.
HolyBlackCat

1
@RianQuinn che eredita più volte dalla stessa classe tramite "percorsi" diversi è perfettamente valido in C ++. Se vuoi creare una struttura a diamante, cioè avere la classe base una sola volta, devi usare l'eredità virtuale. Ma se non vuoi un diamante e avere una classe base duplicata non è un problema per te, anche questo non è un problema per la lingua. Il codice OP produce solo un avviso, dicendo che non è possibile accedere al secondo A, ereditato Bda. Questo va bene. Solo se effettivamente provi ad accedervi, come nel tuo esempio, ricevi un errore.
sebrockm,

Risposte:


4

Perché il compilatore aggiunge un riempimento di byte dopo la struttura A

F {vptr (8) + 0 membri da A + 1 (poiché A è vuoto) +0 da b} = 9, quindi il compilatore aggiunge 7 byte di riempimento per allineare la memorizzazione della struttura;

E {vptr (8) + 0 membri per A} = 8 Nessun riempimento richiesto

da Microsoft

Ogni oggetto dati ha un requisito di allineamento. Per le strutture, il requisito è il più grande dei suoi membri. A ogni oggetto viene assegnato uno scostamento in modo tale che% di allineamento requisito == 0

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

MODIFICARE:

ecco la mia 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';

}

produzione:

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

come puoi vedere a & b non si sovrappone mai tra loro e con vptr su ereditarietà multipla ognuno ha il suo valore di puntatore

nota compilata dalla build VC2019 x64


Non penso che funzioni così. Anche se Anon ha membri, occupa comunque 1 byte (che può essere condiviso con un altro oggetto). In E, Anon si trova dopo il vptr; si sovrappone al primo byte di vptr. (Ecco una demo ; ho leggermente modificato il codice per renderlo Aaccessibile). Lo stesso accade per il primo Ain F. Dal momento che A(e B) possono essere posizionati in cima al vptr, non sono sicuro del perché non accada B.
HolyBlackCat

@HolyBlackCat ma questo è quello che è successo verifica codice test
Ahmed Anter

Ah, quindi MSVC si comporta diversamente da GCC / Clang qui; non è abbastanza intelligente da mettere Ain cima al vptr. Può spiegare perché l'output è 16 su MSVC, ma non sono sicuro di cosa stia succedendo con GCC & Clang.
HolyBlackCat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.