4

Could someone explain to me why adding a virtual function to the end of a class declaration avoids binary incompatibility?

If I have:

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

And later modify this class declaration to:

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void someFuncC() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
};

I get a coredump from another .so compiled against the previous declaration. But if I put someFuncC() at the end of the class declaration (after "int someVal"):

class A
{ 
  public:
    virtual ~A();
    virtual void someFuncA() = 0;
    virtual void someFuncB() = 0;
    virtual void other1() = 0;
  private:
    int someVal;
  public:
    virtual void someFuncC() = 0;
};

I don't see coredump anymore. Could someone tell me why this is? And does this trick always work?

PS. compiler is gcc, does this work with other compilers?

linuxbuild
  • 15,843
  • 6
  • 60
  • 87
bob
  • 1,941
  • 6
  • 26
  • 36

4 Answers4

6

From another answer:

Whether this leads to a memory leak, wipes your hard disk, gets you pregnant, makes nasty Nasal Demons chasing you around your apartment, or lets everything work fine with no apparent problems, is undefined. It might be this way with one compiler, and change with another, change with a new compiler version, with each new compilation, with the moon phases, your mood, or depending on the number or neutrinos that passed through the processor on the last sunny afternoon. Or it might not.

All that, and an infinite amount of other possibilities are put into one term: Undefined behavior:

Just stay away from it.

If you know how a particular compiler version implements its features, you might get this to work. Or you might not. Or you might think it works, but it breaks, but only if whoever sits in front of the computer just ate a yogurt.

Is there any reason why you want this to work anyway?

I suppose it works/doesn't work the way it does/doesn't because your compiler creates virtual table entries in the order the virtual functions are declared. If you mess up the order by putting a virtual function in between the others, then when someone calls other1(), instead someFuncC() is called, possibly with wrong arguments, but definitely at the wrong moment. (Be glad it crashes immediately.)
This is, however, just a guess, and even if it is a right one, unless your gcc version comes with a document somewhere describing this, there's no guarantee it will work this way tomorrow even with the same compiler version.

Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445
  • +1 Coincidentally, I am eating yogurt right now, so should I try and see if g++ lets me get away with some nasty undefined stuff? :) – fredoverflow May 18 '10 at 18:26
  • @FredOverflow: Then just add to the manual that users are required to eat yogurt while using your application and a good deal with a big dairy might make you more money than the application itself. `:-o` – sbi May 18 '10 at 18:31
  • Hm, wouldn't it be sufficient if I eat yogurt during compilation? Is undefined behavior associated with compile-time, runtime or both? Or is that also undefined? :) – fredoverflow May 18 '10 at 18:39
  • @FredOverflow: I believe it's the behavior of the generated executable that's undefined, but I think a compiler might still be standard-conforming if it rejected code that leads to undefined behavior. At least with some cases of UB. Or would it? `` I'm the exact opposite of a lawyer... – sbi May 18 '10 at 18:43
  • It would be nice if compilers could warn more about undefined behavior. `g++ --nose-exorcist` or something like that. – fredoverflow May 18 '10 at 18:49
2

I'm a bit surprised that this particular rearrangement helps at all. It's certainly not guaranteed to work.

The class you give above will normally be translated to something on this order:

typedef void (*vfunc)(void);

struct __A__impl { 
     vfunc __vtable_ptr;
     int someVal;
};

__A__impl__init(__A__impl *object) { 
    static vfunc virtual_functions[] = { __A__dtor, __A__someFuncA, __A__someFuncB};
    object->__vtable__ptr = virtual_functions;    
}

When/if you add someFuncC, you should normally get another entry added to the class' virtual functions table. If the compiler arranges that before any of the other functions, you'll run into a problem where attempting to invoke one function actually invokes another. As long as its address is at the end of the virtual function table, things should still work. C++ doesn't guarantee anything about how vtables are arranged though (or even that there is a vtable).

With respect to normal data, (non-static) members are required to be arranged in ascending order as long as there isn't an intervening access specificer (public:, protected: or private:).

If the compiler followed the same rules when mapping virtual function declarations to vtable positions, your first attempt should work, but your second could break. Obviously enough, there's no guarantee of that though -- as long as it works consistently, the compiler can arrange the vtable about any way it wants to.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • updated problem description. I assume adding to the end of the class declaration works because gcc will add the new virtual function to the end of the vTable? – bob May 18 '10 at 18:35
  • @bob: as long as its declaration follows the others, I'd kind of expect that anyway. Noah Roberts may be right though -- maybe it's somehow related to making it private, so perhaps it doesn't get put into the normal vtable at all (it would be sort of unnecessary there, since polymorphic invocation doesn't make much sense for a function that can only exist in one class). – Jerry Coffin May 18 '10 at 18:43
  • Shouldn't `__A_impl::__vtable_ptr` be a pointer to `vfunc`? I think a `*` is missing there. – Fabio A. Apr 01 '16 at 14:09
  • @FabioA.: No. The vtable pointer is a pointer to a table. Each element in the table is a pointer to a function (but it's not *exactly* as above--each pointer can be to a different type of function). – Jerry Coffin Apr 01 '16 at 15:09
  • @JerryCoffin, that's what I'm saying. In your typedef, `vfunc` is a pointer to function, therefore `__A_impl::__vtable_ptr` should be an array of `vfunc`'s, which means it should be a pointer to pointers of functions, which means a `*` is missing. – Fabio A. Apr 04 '16 at 09:10
  • @FabioA.: Oh, I see what you're getting at. I was thinking in terms of a typical case where you take the address of the first element of the array, so the pointer has type "pointer to element of the array", not "pointer to array". Since it's only used/manipulated internally, there's not much in the way of meaningful difference between the two though. – Jerry Coffin Apr 04 '16 at 15:14
  • @JerryCoffin: actually the assignment `object->__vtable_ptr = virtual_functions` would be an error in C++ and would emit a warning in C: `virtual_functions` is an array that decays to a pointer of **different type** than `__vtable_ptr`. You can check it for yourself [here](http://coliru.stacked-crooked.com/a/9122acedb0ad1768). – Fabio A. Apr 05 '16 at 08:31
1

Maybe G++ puts its "private" virtual table in a different place than its public one.

At any rate, if you can somehow trick the compiler into thinking an object looks differently than it really does and then use that object you are going to invoke nasal demons. You can't depend on anything they do. This is basically what you are doing.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
-3

Binary compatibility is a bad thing. You should use a plaintext-based system, perhaps XML.

Edit: Misplaced the meaning of your question a little. Windows provides many native ways to share data, for example GetProcAddress, __declspec(dllexport) and __declspec(dllimport). GCC must offer something similar. Binary serialization is a bad thing.

Edit again: Actually, he didn't mention executables in his post. At all. Nor what he was trying to use his binary compatibility for.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 2
    The question is about C++ executable code compatibility, not about data serialization compatibility. – Jeremy Friesner May 18 '10 at 18:30
  • I'd like to be pointed at a dynamic library linking system using plain-text to link executables. _Please._ – sbi May 18 '10 at 18:46
  • Ah. I missed that. Thought he was discussing binary serialization, say to file. Edited my answer. – Puppy May 18 '10 at 19:21