Simple explanations when you look closer
Say you have a project where you’re creating a static lib file, and sharing the header files on projects which use this new lib file of yours, with a simple header like this (Foo.h):
struct Foo {
#ifdef MY_PREPROCESSOR_FLAG
int myFlags;
#endif
int myInteger;
int myOtherInteger;
};
I know this might seem obvious, but make sure that if you define MY_PREPROCESSOR_FLAG in one project, that it is also defined at compile time of the other project! What caught me out was that the static lib and the project which uses the static lib share the same physical header file, but don’t share project settings in the same nature. So now you can easily see what happens if you have MY_PREPROCESSOR_FLAG defined in one project and not the other – in one of the projects sizeof(Foo) is 12 bytes, and in the other sizeof(Foo) is 8 bytes (on a 32bit machine).
The interesting thing – that might catch you out (it got me), is that assignments and copies will probably still work without crashing if you’re only writing to member variables directly (i.e. no function calls or vtable to crash over). In my case, I was only writing to members between this and this+8 (8 being the smaller size struct, so writes never really went out of bounds). Where the caller was expecting to have provided a Foo object where myInteger was 2 and myOtherInteger was 3, having a look at the disassembly output for myStaticLibApiFunc gives you the face palm moment (it gave me mine, I always prefer to look at assembler output, its not the most productive but I find I never have to second guess what the code is meant to be doing vs. what is actually running).
Anyway, from the assembly listing you can easily see the constant offsets on the stack register are off by the size of the #def guarded members.
(in dependant project, where MY_PREPROCESSOR_FLAG is defined)
myStaticLibApiFunc( Foo(1, 2, 3) )
(the function in the static project where MY_PREPROCESSOR_FLAG isn’t defined)void myStaticLibApiFunc( Foo f )
{
Foo x = f;
print("%d", f.myInteger, f.myOtherInteger);
}
if the two compiled version of Foo matched and were 12 bytes, the generated assembly might be something like not the offsets in bold (this is just an quick hand coded approx, not compiler output so please forgive the assumptions!):void myStaticLibApiFunc( Foo f )
{
mov $esp,$ebp
//Assuming Foo x is located 24 bytes before esp,
//and f is the 12 bytes following x
//x.myFlags = f.myFlags
mov 0x0c($ebp), $edx
mov $edx, 0x18($ebp)
//x.myInteger = f.myInteger
mov 0x08($ebp), $edx
mov $edx, 0x14($ebp)
//x.myOtherInteger = f.myOtherInteger
mov 0x04($ebp), $edx
mov $edx, 0x10($ebp)
}
but what you’ve ended up with is this suspicously shorter version (missing the first assignment, and having the “wrong” offsets for myInteger and myOtherInteger
void myStaticLibApiFunc( Foo f )
{
mov $esp,$ebp
//Assuming Foo x is located 16 bytes before esp,
//and f is the 8 bytes following x
//x.??? = f.myFlags
//x.myInteger = f.myInteger
mov 0x08($ebp), $edx
mov $edx, 0x10($ebp)
//x.myOtherInteger = f.myOtherInteger
mov 0x04($ebp), $edx
mov $edx, 0x08($ebp)
}
