[Bug c++/108216] New: Wrong offset for (already-constructed) virtual base during construction of full object

arthur.j.odwyer at gmail dot com gcc-bugzilla@gcc.gnu.org
Fri Dec 23 19:17:56 GMT 2022


https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108216

            Bug ID: 108216
           Summary: Wrong offset for (already-constructed) virtual base
                    during construction of full object
           Product: gcc
           Version: 13.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: c++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: arthur.j.odwyer at gmail dot com
  Target Milestone: ---

// https://godbolt.org/z/6qMTY6bGn
#include <cstdio>

struct A *ga = nullptr;
struct B *gb = nullptr;
struct C *gc = nullptr;
struct D *gd = nullptr;

struct A {
    explicit A() {
        printf("Constructing A at %p\n", (void*)this);
        ga = this;
        printf(" A is %p\n", (void*)ga);
    }
    virtual void f() {}
    void *a() { return this; }
};

struct B : virtual A {
    explicit B() {
        printf("Constructing B at %p\n", (void*)this);
        gb = this;
        printf(" B.A is %p\n", (void*)(A*)gb);
    }
    void *b() { return this; }
};

struct C : virtual A {
    explicit C() {
        printf("Constructing C at %p\n", (void*)this);
        gc = this;
        printf(" B.A is %p -- look here!\n", (void*)(A*)gb);
        printf(" C.A is %p\n", (void*)(A*)gc);
    }
    // void f() override {}  // give Clang trouble, too
    void *c() { return this; }
};

struct D : B, C {
    explicit D(): B(), C() {
        printf("Constructing D at %p\n", (void*)this);
        gd = this;
        printf(" D.B.A is %p\n", (void*)(A*)(B*)gd);
        printf(" D.C.A is %p\n", (void*)(A*)(C*)gd);
    }
};

int main() {
    D d;
    printf("&D is %p\n", (void*)&d);
    printf("&C is %p\n", d.c());
    printf("&B is %p\n", d.b());
    printf("&A is %p\n", d.a());
}

======

Constructing A at 0x7ffd2ef4db10
 A is 0x7ffd2ef4db10
Constructing B at 0x7ffd2ef4db10
 B.A is 0x7ffd2ef4db10
Constructing C at 0x7ffd2ef4db18
 B.A is 0x7ffd2f34ed1c -- look here!
 C.A is 0x7ffd2ef4db10
Constructing D at 0x7ffd2ef4db10
 D.B.A is 0x7ffd2ef4db10
 D.C.A is 0x7ffd2ef4db10
&D is 0x7ffd2ef4db10
&C is 0x7ffd2ef4db18
&B is 0x7ffd2ef4db10
&A is 0x7ffd2ef4db10

======

Before the line marked "look here":
- The `A` object was constructed at 0x7ffd2ef4db10.
- The `B` object pointed to by `gb` has been completely constructed.
- So `gb->a()` ought to return the address of that `A` object, 0x7ffd2ef4db10.
But instead it returns 0x7ffd2f34ed1c, which is 0x40120c bytes away from the
correct value!

I wonder if this is caused by the B-in-D and C-in-D vptrs having the same
offset, so that when we think we're access the B vtable of `*gb`, we're
actually accessing the C vtable of that empty C object...? But then, still, the
offset from the beginning of the B object or the beginning of the C object, to
the A virtual base, ought to be exactly the same number. I can't figure out a
reason for the answer to be off by 0x40120c.

======

Notice that Clang passes this test case as shown; BUT, if you uncomment the
line marked "give Clang trouble, too", then Clang will join GCC in producing
wrong results for the line marked "look here".  MSVC passes both test cases,
but that's not surprising because MSVC has a radically different ABI for struct
layout.

Originally reported by @caster on Slack, here:
https://cpplang.slack.com/archives/CBTFTLR9R/p1671750342552189


More information about the Gcc-bugs mailing list