I have a piece of legacy C++ code which lives in a library and is loaded as DLL by an application:
// Foobar.h (old)
struct Foobar
{
const char* foo;
const char* bar;
int apple;
int banana;
};
// Foobar.cpp (old)
void func(Foobar* ptrFoobar) {
cout << ptrFoobar -> foo << endl;
cout << ptrFoobar -> bar << endl;
cout << ptrFoobar -> apple << endl;
cout << ptrFoobar -> banana << endl;
}
And I am hoping to add a new field to the end of this struct in the new release for this library (which I know breaks the ABI):
// Foobar.h (new)
struct Foobar
{
const char* foo;
const char* bar;
int apple;
int banana;
int orange
};
// Foobar.cpp (new)
void func(Foobar* ptrFoobar) {
cout << ptrFoobar -> foo << endl;
cout << ptrFoobar -> bar << endl;
cout << ptrFoobar -> apple << endl;
cout << ptrFoobar -> banana << endl;
cout << ptrFoobar -> orange << endl;
}
Suppose it's known that the only way how this library is used by the application is:
// app.cpp (linked with new library)
Foobar foobar { ... };
func(&foobar);
And app.cpp is linked with the new library (the one with extra member variable orange
).
While I know it is unsafe in general to append a member variable to a C++ class, since it breaks the ABI in many terrible ways, so code above should never have been written in the first place, I am still wondering under the extremely restrictive conditions:
- The new field
orange
is appended to the end of the struct - The fields in the struct are ordered by their sizeof(type) value non-increasingly.
- The only way how
Foobar
is used in application code is constructing a new object (using the new Foobar.h) and passing it to func(..) by pointer. And the library (either the old or new one) does not do anything more than reading the value of known member variables as shown in the samples above (so no malloc, no sizeof, no other crazy pointer maths).
Is it safe for the application (linked against the new library) to load the old binary at runtime?