Recently I have been trying to make a plugin for an old game, and running into a problem similar to Diamond Inheritance.
I have a very reduced example, write as follows:
#include <iostream>
#include <stdint.h>
#include <stddef.h>
using namespace std;
struct CBaseEntity
{
virtual void Spawn() = 0;
virtual void Think() = 0;
int64_t m_ivar{};
};
struct CBaseWeapon : virtual public CBaseEntity
{
virtual void ItemPostFrame() = 0;
double m_flvar{};
};
struct Prefab : virtual public CBaseEntity
{
void Spawn() override { cout << "Prefab::Spawn\n"; }
void Think() override { cout << "Prefab::Think\n"; }
};
struct WeaponPrefab : virtual public CBaseWeapon, virtual public Prefab
{
void Spawn() override { cout << boolalpha << m_ivar << '\n'; }
void ItemPostFrame() override { m_flvar += 1; cout << m_flvar << '\n'; }
char words[8];
};
int main() noexcept
{
cout << sizeof(CBaseEntity) << '\n';
cout << sizeof(CBaseWeapon) << '\n';
cout << sizeof(Prefab) << '\n';
cout << sizeof(WeaponPrefab) << '\n';
cout << offsetof(WeaponPrefab, words) << '\n';
}
The first two are extracted from the game's source code and I made them pure virtual classes since I have no need to instantiate them.
The third class (Prefab
) is the one I extend all my classes in my mod from.
The problem is:
I just noticed that the class size changed, which could potentially indicate an ABI-breaking thingy waiting for me. When I removed all virtual
keywords from inheritances, the class size is quite small, and the memory layout make sense to me. But whenever I put virtual
inheritance on, the size suddenly blows up, and the layout seems like a mystery.
Like I printed out the offsetof
a variable in my WeaponPrefab
class, it shows 8
, but the total size is 48
, which doesn't make any sense - where are the m_ivar
and m_flvar
?
(I am not trying to provoke people with undefined behavior, but just trying to cope with the existing ABI in the original game.)
Link to Compiler Explorer: https://godbolt.org/z/YvWTbf8j8
Vptr
andVtable
in C++. When you have virtual methods, the size of the class will be greater by size of theVptr
, which will be 4 bytes on x86 machine and 8 bytes on x64.offsetof(WeaponPrefab, words)
returns 8, because the first 8 bytes are occupied byVptr
. – TorrietorrinWeaponPrefab : virtual public CBaseWeapon, virtual public Prefab
You don't needvirtual
here (unless you plan to extend your inheritance lattice hierarchy further—not recommended). – Lymphocyteoffsetof
m_flvar
andm_ilvar
? Trying that one might give you some clues. – Bradstreet