Tại sao không lưu trữ cho một cơ sở trống trùng lặp trùng lặp với một con trỏ vtable?


11

Xem xét ví dụ này:

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

(chạy trên godbolt)

Ở đây bạn có thể thấy rằng đối với struct Elớp cơ sở trống (lớn 1 byte) sử dụng cùng một bộ lưu trữ như con trỏ vtable, như mong đợi.

Nhưng đối với struct Fmột cơ sở trống trùng lặp, điều này không xảy ra. Điều gì gây ra điều này?

Tôi nhận được kết quả tương tự trên GCC, Clang và MSVC. Các kết quả trên là cho x64, vì vậy sizeof(void *) == 8.


Thật thú vị, đối với struct G : A, B {void *ptr;};GCC và Clang thực hiện EBO (kích thước là 8), nhưng MSVC thì không (kích thước là 16).


3
Thật kỳ lạ, bằng cách kế thừa từ C(kế thừa từ ) A, Bbạn sẽ nhận được kết quả khác với hình thức kế thừa ABtrực tiếp
Guillaume Racicot

1
Tôi rất thích nghiên cứu cái này. Cảm ơn câu hỏi và liên kết. Tôi không chắc mình có câu trả lời và vì vậy sẽ chỉ bình luận. Có thể là điều này phát sinh từ sự mơ hồ được giới thiệu từ sự phát sinh của CF? Sau tất cả, 2 * sizeof(void*) == 16trên x86_64 như bạn đã nói. Trình biên dịch không thể tối ưu hóa hoàn toàn (như Story Teller đã nói) và vì vậy không.
Andrew Falanga

2
Điều bình thường là bạn nhận được kết quả tương tự trên gcc và clang, vì cả hai đều theo itanium ABI. Và nếu đây là trường hợp tôi nghĩ, khi xác định ABI, họ sợ thuật toán bố cục có thể trở nên quá đắt, vì vậy họ đã sử dụng một số phím tắt (còn gọi là bi quan).
Marc Glisse

2
@RianQuinn Một cơ sở trùng lặp không làm cho cấu trúc không hợp lệ.
HolyBlackCat

1
@RianQuinn kế thừa từ cùng một lớp nhiều lần thông qua các "đường dẫn" khác nhau là hoàn toàn hợp lệ trong C ++. Nếu bạn muốn tạo cấu trúc kim cương, tức là chỉ có lớp cơ sở một lần, bạn phải sử dụng kế thừa ảo. Nhưng nếu bạn không muốn có một viên kim cương và có một lớp cơ sở trùng lặp không phải là vấn đề với bạn, thì đây cũng không phải là vấn đề đối với ngôn ngữ. Mã của OP chỉ tạo ra một cảnh báo, nói rằng thứ hai A, được kế thừa qua B, không thể truy cập được. Điều đó là tốt. Chỉ khi bạn thực sự cố gắng truy cập nó, như trong ví dụ của bạn, bạn sẽ gặp lỗi.
sebrockm

Câu trả lời:


4

Bởi vì trình biên dịch thêm một byte đệm sau cấu trúc A

F {vptr (8) + 0 thành viên từ phần đệm A + 1 (vì A trống) +0 từ b} = 9 sau đó trình biên dịch thêm phần đệm 7 byte để căn chỉnh lưu trữ của struct;

E {vptr (8) + 0 thành viên cho A} = 8 Không yêu cầu đệm

từ Microsoft

Mỗi đối tượng dữ liệu có một yêu cầu căn chỉnh. Đối với các cấu trúc, yêu cầu là lớn nhất trong số các thành viên của nó. Mỗi đối tượng được phân bổ một phần bù sao cho phần bù% căn chỉnh-request == 0

https://docs.microsoft.com/en-us/cpp/c-lingu/st Storage-and-alocate-of- cấu trúc? view = vs-2019

BIÊN TẬP:

đây là bản demo của tôi:

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

}

đầu ra:

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

như bạn có thể thấy a & b không bao giờ trùng lặp với nhau và với vptr trên nhiều kế thừa, mỗi cái có giá trị con trỏ riêng

lưu ý được biên soạn bởi bản dựng VC2019 x64


Tôi không nghĩ rằng đây là cách nó hoạt động. Mặc dù Akhông có thành viên, nó vẫn chiếm 1 byte (có thể được chia sẻ với một đối tượng khác). Trong E, Akhông nằm sau vptr; nó chồng lên byte đầu tiên của vptr. (Đây là bản demo ; Tôi đã sửa đổi mã một chút để có Athể truy cập được.). Điều tương tự xảy ra cho lần đầu tiên Atrong F. Vì A(và B) có thể được đặt trên đầu của vptr, tôi không chắc tại sao điều đó không xảy ra B.
HolyBlackCat

@HolyBlackCat nhưng đây là những gì đã xảy ra kiểm tra mã kiểm tra
Ahmed Anter

Aha, vì vậy MSVC hành xử khác với GCC / Clang ở đây; nó không đủ thông minh để đặt Alên trên vptr. Nó có thể giải thích tại sao đầu ra là 16 trên MSVC, nhưng tôi không chắc điều gì đang xảy ra với GCC & Clang.
HolyBlackCat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.