为什么不为重复的空基存储与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?
}

(运行在boltbolt上)

在这里,您可以看到,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(继承自AB),您得到的结果AB直接继承形式的结果有所不同
Guillaume Racicot

1
我喜欢研究这个。感谢您的问题和链接。我不确定我是否有答案,所以只评论一下。难道这是由C和的推导引入的歧义引起的F?毕竟,2 * sizeof(void*) == 16正如您所说,在x86_64上。编译器无法完全优化(如Story Teller所说),因此也不能。
安德鲁·法兰加

2
在gcc和clang上得到相同的结果是正常的,因为它们都遵循itanium ABI。如果是这种情况,我认为在定义ABI时,他们担心布局算法可能会变得过于昂贵,因此他们采取了一些捷径(也就是悲观化)。
Marc Glisse

2
@RianQuinn重复的碱基不会使结构无效。
HolyBlackCat

1
@RianQuinn通过不同的“路径”从同一个类多次继承在C ++中是完全有效的。如果要创建菱形结构,即仅一次拥有基类,则必须使用虚拟继承。但是,如果您不想要钻石,并且拥有重复的基类对您来说不成问题,那么对于该语言而言也就不成问题。OP的代码仅产生警告,表示无法访问A通过继承的第二个B。那样就好。仅当您确实尝试访问它时(如您的示例),您才会收到错误。
sebrockm

Answers:


4

因为编译器在结构A之后添加了一个字节填充

F {vptr(8)+来自A的0个成员+ 1个填充(因为A为空)+来自b的0} = 9,然后编译器添加7个字节的填充以对齐结构的存储;

E {vptr(8)+ 0个A的成员} = 8不需要填充

来自微软

每个数据对象都有一个对齐要求。对于结构,要求是最大的成员。为每个对象分配一个偏移量,以使偏移量%alignment-requirement == 0

https://docs.microsoft.com/zh-cn/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个字节(可以与其他对象共享)。在中EA位于vptr之后;它与vptr的第一个字节重叠。(这里是一个演示;我对代码进行了一些修改以使其A易于访问。)。同样的情况发生AF。由于A(和B)可以放在vptr的顶部,因此我不确定为什么不会发生B
HolyBlackCat

@HolyBlackCat,但这就是所发生的检查测试代码
艾哈迈德·

啊哈,所以MSVC在这里的行为不同于GCC / Clang;它不够聪明,无法放在Avptr之上。它可以解释为什么在MSVC上输出为16,但是我不确定GCC和Clang发生了什么。
HolyBlackCat
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.