I'm using C code in my C++ project which I include using extern "C". I then define structs in my C++ class which are passed to the C code (compiled using gcc). The problem is that when that C code accesses the struct that was defined in the C++ class, I get different alignment and the code reads and writes WRONG data. This seems bizarre but I have been debugging this for 3 hours and basically when I print pointer to a deep member of that struct from C++ code, everything is fine. But if I print the pointer from within C code, the address is different!
Why is this happening and how do I ensure that my alignment is consistent?
(I have double checked that this is NOT an out of date compilation issue by recompiling everything. Still same result. In C the address is 8 bytes before the address to the member as reported in C++ code)
Edit: the problem was that g++ and gcc would treat empty structs differently. If I had an empty struct foo {} in my code, sizeof my struct would be 1 byte bigger under g++ and all offsets of members would be messed up. So I put a dummy uint8_t into the empty struct so that it would not be empty anymore and all such problems across the codebase seem to have disappeared. Now alignment seems to work the same and struct size is the same as well (for each empty struct g++ would previously add 1 byte). I am not 100% sure what is going on with gcc so if anyone is has anything to add then feel free to fill me in on this
My gcc version is:
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/5/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 5.4.0-6ubuntu1~16.04.5' --with-bugurl=file:///usr/share/doc/gcc-5/README.Bugs --enable-languages=c,ada,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-5 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 5.4.0 20160609
My compilation flags under gcc are:
-std=gnu99 -Wchar-subscripts -Wformat -Wformat-nonliteral -Wformat-security -Wmissing-braces -Wparentheses -Wsequence-point -Wswitch -Wtrigraphs -Wno-unused-function -Wunused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wuninitialized -Wdiv-by-zero -Wfloat-equal -Wdouble-promotion -fsingle-precis
ion-constant -Wshadow -Wpointer-arith -Wwrite-strings -Wconversion -Wredundant-decls -Wunreachable-code -Winline -Wenum-compare -Wlong-long -Wchar-subscripts -Wextra -Werror -g -Os -ffunction-sections -fdata-sections -m32 -fstack-protector -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -ftest-coverage -fprofile-arcs
g++
-std=c++11 -Wextra -Werror -g -Os -ffunction-sections -fdata-sections -m32 -fstack-protector -D_XOPEN_SOURCE=700 -D_GNU_SOURCE -ftest-coverage -fprofile-arcs
Edit2: added -pedantic to my C flags to make gcc warn about empty structs. Dropped it previously because it broke many other things and third party headers (we have to use -Werror). Luckily things like macro ({}) statements can be prefixed with __extension__ to make them pass -pedantic. Got a few other things that need to be changed in the codebase. Don't know yet if something will be hard to implement with pedantic enabled. Does anyone know how to only enable warning for empty structs or disable some specific things that pedantic does not allow?
The proper solution seems to be to never define empty structs in code that will be used across C++ -> C boundary. I had a:
struct spinlock { }
that was empty when building against a single processor target. So g++ would make this 1 byte while gcc would just discard it. Therefore my structs would be different size and offsetof would even return different results depending on whether the code was built with gcc or g++.
So I changed it to
struct spinlock { uint8_t dummy; }
and now my structs align perfectly and everything works without any special C++ standard specification.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With