13

My library has two classes, a base class and a derived class. In the current version of the library the base class has a virtual function foo(), and the derived class does not override it. In the next version I'd like the derived class to override it. Does this break ABI? I know that introducing a new virtual function usually does, but this seems like a special case. My intuition is that it should be changing an offset in the vtbl, without actually changing the table's size.

Obviously since the C++ standard doesn't mandate a particular ABI this question is somewhat platform specific, but in practice what breaks and maintains ABI is similar across most compilers. I'm interested in GCC's behavior, but the more compilers people can answer for the more useful this question will be ;)

Joseph Garvin
  • 20,727
  • 18
  • 94
  • 165

5 Answers5

11

It might.

You're wrong regarding the offset. The offset in the vtable is determined already. What will happen is that the Derived class constructor will replace the function pointer at that offset with the Derived override (by switching the in-class v-pointer to a new v-table). So it is, normally, ABI compatible.

There might be an issue though, because of optimization, and especially the devirtualization of function calls.

Normally, when you call a virtual function, the compiler introduces a lookup in the vtable via the vpointer. However, if it can deduce (statically) what the exact type of the object is, it can also deduce the exact function to call and shave off the virtual lookup.

Example:

struct Base {
  virtual void foo();
  virtual void bar();
};

struct Derived: Base {
  virtual void foo();
};

int main(int argc, char* argv[]) {
  Derived d;
  d.foo(); // It is necessarily Derived::foo
  d.bar(); // It is necessarily Base::bar
}

And in this case... simply linking with your new library will not pick up Derived::bar.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    That's why the virtual methods should always be private. – p12 Feb 19 '12 at 16:51
  • 1
    @grumm143: This is not sufficient though. Inlining can still kick in. – Matthieu M. Feb 19 '12 at 16:56
  • How? Virtual methods could only be called though public, non-virtual interface, which doesn't depend on the exact type of the class. The virtual calls could be inlined only within the library itself, but this obviously isn't an issue. – p12 Feb 19 '12 at 18:24
  • @grumm143: Ah, then you would mean that public methods definitions are not exposed (to prevent inlining). – Matthieu M. Feb 19 '12 at 20:07
  • 1
    "_the Derived class constructor will replace the function pointer at that offset with the Derived override_" the constructor does not mess with the vtable. In fact the vtable are constants (but could need fixing by the dynamic linker at load time). **A vtable never dynamically modified.** – curiousguy Aug 07 '12 at 06:06
  • @grumm143 "_That's why the virtual methods should always be private._" hug? – curiousguy Aug 07 '12 at 06:07
  • @curiousguy: right, poorly exprimed, what is changed is the v-pointer; I added a clarification. – Matthieu M. Aug 07 '12 at 06:48
7

This doesn't seem like something that could be particularly relied on in general - as you said C++ ABI is pretty tricky (even down to compiler options).

That said I think you could use g++ -fdump-class-hierarchy before and after you made the change to see if either the parent or child vtables change in structure. If they don't it's probably "fairly" safe to assume you didn't break ABI.

Mark B
  • 95,107
  • 10
  • 109
  • 188
3

Yes, in some situations, adding a reimplementation of a virtual function will change the layout of the virtual function table. That is the case if you're reimplementing a virtual function from a base that isn't the first base class (multiple-inheritance):

// V1
struct A { virtual void f(); };
struct B { virtual void g(); };
struct C : A, B { virtual void h(); }; //does not reimplement f or g;

// V2
struct C : A, B {
    virtual void h();
    virtual void g();  //added reimplementation of g()
};

This changes the layout of C's vtable by adding an entry for g() (thanks to "Gof" for bringing this to my attention in the first place, as a comment in http://marcmutz.wordpress.com/2010/07/25/bcsc-gotcha-reimplementing-a-virtual-function/).

Also, as mentioned elsewhere, you get a problem if the class you're overriding the function in is used by users of your library in a way where the static type is equal to the dynamic type. This can be the case after you new'ed it:

MyClass * c = new MyClass;
c->myVirtualFunction(); // not actually virtual at runtime

or created it on the stack:

MyClass c;
c.myVirtualFunction(); // not actually virtual at runtime

The reason for this is an optimisation called "de-virtualisation". If the compiler can prove, at compile time, what the dynamic type of the object is, it will not emit the indirection through the virtual function table, but instead call the correct function directly.

Now, if users compiled against an old version of you library, the compiler will have inserted a call to the most-derived reimplementation of the virtual method. If, in a newer version of your library, you override this virtual function in a more-derived class, code compiled against the old library will still call the old function, whereas new code or code where the compiler could not prove the dynamic type of the object at compile time, will go through the virtual function table. So, a given instance of the class may be confronted, at runtime, with calls to the base class' function that it cannot intercept, potentially creating violations of class invariants.

Marc Mutz - mmutz
  • 24,485
  • 12
  • 80
  • 90
  • Thanks for sharing this info. In my mind [Mark's](http://stackoverflow.com/questions/5746076/in-c-does-overriding-an-existing-virtual-function-break-abi/5746168#5746168) suggestion to use `g++ -fdump-class-hierarchy` would be the winner here, right after having proper regression tests ;) – sehe Apr 26 '11 at 13:16
1

My intuition is that it should be changing an offset in the vtbl, without actually changing the table's size.

Well, your intuition is clearly wrong:

  • either there is a new entry in the vtable for the overrider, all following entries are moved, and the table grows,
  • or there is no new entry, and the vtable representation does not change.

Which one is true can depends on many factors.

Anyway: do not count on it.

curiousguy
  • 8,038
  • 2
  • 40
  • 58
0

Caution: see In C++, does overriding an existing virtual function break ABI? for a case where this logic doesn't hold true;

In my mind Mark's suggestion to use g++ -fdump-class-hierarchy would be the winner here, right after having proper regression tests


Overriding things should not change vtable layout[1]. The vtable entries itself would be in the datasegment of the library, IMHO, so a change to it should not pose a problem.

Of course, the applications need to be relinked, otherwise there is a potential for breakage if the consumer had been using direct reference to &Derived::overriddenMethod; I'm not sure whether a compiler would have been allowed to resolve that to &Base::overriddenMethod at all, but better safe than sorry.

[1] spelling it out: this presumes that the method was virtual to begin with!

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • "_Overriding things should not change vtable layout" Wrong. It depends. – curiousguy Aug 07 '12 at 06:09
  • @curiousguy I thought I made that very clear. Also I link to relevant resources to reach a verdict based on the actual context. Because... it depends on the actual context. – sehe Aug 07 '12 at 11:42
  • "_I thought I made that very clear._" Not clear for me, sorry. Are you saying that, for single inheritance, adding an overrider does not change the layout of the vtable? – curiousguy Aug 07 '12 at 18:58
  • @curiousguy yes, as that is my understanding. If that's not correct, why don't you provide an answer describing such cases so we can upvote it? – sehe Aug 08 '12 at 01:45