ทำไมพื้นที่จัดเก็บสำหรับฐานที่ว่างเปล่าซ้ำซ้อนไม่ทับซ้อนกันด้วยตัวชี้ 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?
}

(วิ่งบน godbolt)

ที่นี่คุณจะเห็นว่าสำหรับ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(ซึ่งสืบทอดมาจากA, B) คุณจะได้รับผลที่แตกต่างกันกว่าสืบทอดรูปแบบAและBโดยตรง
กีโยม Racicot

1
ฉันสนุกกับการค้นคว้าสิ่งนี้ ขอบคุณสำหรับคำถามและลิงค์ ฉันไม่แน่ใจว่าฉันมีคำตอบและเพียงแค่แสดงความคิดเห็น เป็นไปได้หรือไม่ว่าสิ่งนี้เกิดขึ้นจากความคลุมเครือที่เกิดจากการได้รับCและF? หลังจากทั้งหมด2 * sizeof(void*) == 16ใน x86_64 ตามที่คุณพูด คอมไพเลอร์ไม่สามารถปรับให้เหมาะสมอย่างเต็มที่ (อย่างที่ผู้เล่าเรื่องราวพูด) และไม่ทำเช่นนั้น
Andrew Falanga

2
เป็นเรื่องปกติที่คุณจะได้รับผลลัพธ์ที่เหมือนกันใน gcc และ clang เนื่องจากทั้งคู่ติดตาม itanium ABI และถ้าเป็นกรณีที่ฉันคิดว่าเมื่อกำหนด ABI พวกเขากลัวอัลกอริทึมเค้าโครงอาจแพงเกินไปดังนั้นพวกเขาจึงใช้ทางลัดบางอย่าง
Marc Glisse

2
@RianQuinn ฐานที่ซ้ำกันไม่ได้ทำให้โครงสร้างไม่ถูกต้อง
HolyBlackCat

1
@RianQuinn ที่สืบทอดมาจากคลาสเดียวกันหลาย ๆ ครั้งผ่าน "เส้นทาง" ที่แตกต่างกันนั้นใช้ได้อย่างสมบูรณ์ใน C ++ หากคุณต้องการสร้างโครงสร้างเพชรเช่นมีคลาสฐานเพียงครั้งเดียวคุณต้องใช้การสืบทอดเสมือน แต่ถ้าคุณไม่ต้องการเพชรและมีคลาสพื้นฐานที่ซ้ำกันก็ไม่มีปัญหาสำหรับคุณนี่ก็ไม่มีปัญหาสำหรับภาษาเช่นกัน รหัสของ OP จะสร้างคำเตือนโดยบอกว่าไม่สามารถเข้าถึงรหัสที่สองที่Aสืบทอดผ่านBได้ นั่นเป็นเรื่องปกติ เฉพาะในกรณีที่คุณพยายามเข้าถึงดังเช่นในตัวอย่างของคุณคุณจะได้รับข้อผิดพลาด
sebrockm

คำตอบ:


4

เนื่องจากคอมไพเลอร์เพิ่มหนึ่ง padding ไบต์หลังจากโครงสร้าง A

F {vptr (8) + 0 สมาชิกจากการเติม A + 1 (เนื่องจาก A ว่างเปล่า) +0 จาก b} = 9 จากนั้นคอมไพเลอร์จะเพิ่มการเว้นวรรค 7 ไบต์เพื่อจัดตำแหน่งหน่วยเก็บของโครงสร้าง

E {vptr (8) + 0 สมาชิกสำหรับ A} = 8 ไม่ต้องใช้ช่องว่างภายใน

จาก Microsoft

วัตถุข้อมูลทุกตัวมีความต้องการการจัดตำแหน่ง สำหรับโครงสร้างความต้องการคือสิ่งที่ใหญ่ที่สุดของสมาชิก ทุกวัตถุถูกจัดสรรออฟเซ็ตเพื่อให้ออฟเซ็ตการจัดตำแหน่ง% = = 0

https://docs.microsoft.com/en-us/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

ในขณะที่คุณสามารถเห็น & & b ไม่ทับซ้อนกันและมี vptr ในมรดกหลายรายการแต่ละคนมีค่าตัวชี้ของตัวเอง

บันทึกย่อที่รวบรวมโดย VC2019 x64 build


ฉันไม่คิดว่านี่เป็นวิธีการทำงาน แม้ว่าจะAไม่มีสมาชิก แต่ก็ยังคงมีขนาด 1 ไบต์ (ซึ่งสามารถใช้ร่วมกับวัตถุอื่นได้) ในE, Aไม่ได้อยู่หลัง vptr นั้น มันทับซ้อนไบต์แรกของ vptr (นี่คือตัวอย่างฉันได้แก้ไขโค้ดเล็กน้อยเพื่อให้Aสามารถเข้าถึงได้) เดียวกันที่เกิดขึ้นเป็นครั้งแรกในA Fตั้งแต่A(และB) สามารถวางบน vptr Bผมไม่แน่ใจว่าทำไมมันไม่ได้เกิดขึ้น
HolyBlackCat

@HolyBlackCat แต่นี่คือสิ่งที่เกิดขึ้นตรวจสอบรหัสทดสอบ
Ahmed Anter

อืมดังนั้น MSVC จะทำงานแตกต่างจาก GCC / Clang ที่นี่ มันไม่ฉลาดพอที่จะวางAบน vptr มันสามารถอธิบายได้ว่าทำไมผลลัพธ์ถึง 16 ใน MSVC แต่ฉันไม่แน่ใจว่าเกิดอะไรขึ้นกับ GCC & Clang
HolyBlackCat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.