I would ideally assume that compiler/linker would flag this as an error but it doesnt.
You are providing the compiler with different class definitions for the same class for different translation units. This is undefined behavior, so no compiler has to diagnose it. As mentioned in the comments, compiler developers have other worries than the compiler user messing up their definitions in such a way.
On a more technical level, each translation unit emits an object file. The object files are linked together by the linker. But the object files know nothing of classes, only functions. It thus has no explicit knowledge about object sizes or member offsets.
Would it be possible to emit "compiler comments" in the object files to detect this? Or could this be part of debug symbols? Yes, but it would probably introduce significant bloat and increase compile times. Also, there is no requirement that your library binary has any of that, so it doesn't help in that case. So it would be an unreliable assistance in a rare case of the user messing up, with significant downsides.
Why could address sanitizer/valgrind not detect this, as this seems like writing over memory that is not owned by it.
I don't know enough about the inner workings of valgrind to give a good answer here, but presumably the str
access that get
assumes to be where i
actually is inside a
in main
does not seem immediately suspicious to valgrind because the start of str
is still within the memory allocated for A
. If you only get
a single character, small string optimizations might also cause main
to never access A
outside those first few bytes reserved for the int
.
Except not using macros like this in headers, how would one detect this?
It's a horrible idea to use macros in such a way - precisely because these problems are almost impossible to detect. You mentioned some tools yourself that may quickly catch "malign" undefined behavior (e.g. a std::vector trying to manage memory after having its control structure overwritten), and you can both configure your operating system and compiler to instrument your program more stringently to get notified (e.g. -fstack-protector-all
on gcc, /Gs
and /RTCs
on MSVC, these safety features on newer Windows and so on) when it does something dubious.
Nevertheless, this is still undefined behavior we are talking about, so there is no guaranteed way to find the problem, mainly because "everything works as expected when you try to identify problems" is still within the realm of "everything could happen". Or, in other words, there may simply be no symptoms present that are caught by these tools, even when your program still does subtly wrong things.