I am using CMake to generate some build environments:
cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=TRUE
cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=TRUE
cmake .. -DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE -DBUILD_SHARED_LIBS=TRUE -G "Visual Studio 14 2015 Win64"
The first and third environments builds successfully (Whether I select "Debug" or "Release") but the second one has a linker error:
MakeFiles/Stub_Time.dir/src/TimeStub.cpp.o: In function `NotSet ConvertJSON<NotSet>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::shared_ptr<IMessageLog>)':
TimeStub.cpp:(.text._Z11ConvertJSONI6NotSetET_NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt10shared_ptrI11IMessageLogE[_Z11ConvertJSONI6NotSetET_NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEESt10shared_ptrI11IMessageLogE]+0xe4): undefined reference to `void load<cereal::JSONInputArchive>(cereal::JSONInputArchive&, NotSet&)'
../lib/libIO.so: undefined reference to `void save<cereal::JSONOutputArchive>(cereal::JSONOutputArchive&, VehicleBound const&)'
../lib/libIO.so: undefined reference to `void save<cereal::JSONOutputArchive>(cereal::JSONOutputArchive&, Emissions const&)'
collect2: error: ld returned 1 exit status
CMakeFiles/Stub_Time.dir/build.make:111: recipe for target 'bin/Stub_Time' failed
make[2]: *** [bin/Stub_Time] Error 1
CMakeFiles/Makefile2:77: recipe for target 'CMakeFiles/Stub_Time.dir/all' failed
make[1]: *** [CMakeFiles/Stub_Time.dir/all] Error 2
Makefile:94: recipe for target 'all' failed
make: *** [all] Error 2
I tried invoking make VERBOSE=1
and the only differences seemed to be that c++
was being called with the -g
flag in the successful case, and the -O3 -DNDEBUG
flags in the unsuccessful case.
Why would this happen?
Is release mode optimizing-away the symbols in libIO.so
that gets referenced in Stub_Time
? If so, how can I stop it? If not, how can I investigate?
Edit:
I ran:
grep -R ifdef | grep DEBUG
On the whole project, but none of the files that it came up with seemed at all relevant to the above error (just stuff related to printing the version).
Edit 2:
I found that I can modify the flags that CMake passes to c++
by adding the following to CMakeLists.txt:
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG")
Different combinations of these revealed that the error does not occur with-O0
but does occur with -O1
Edit 3
We use a json serialization library: cereal. It looks for functions that resemble the ones below. Typically, you'd have code there that called out certain fields to be serialized. There were some odd cases where we wanted to say: "Serialize Nothing" so we implemented the following serialization calls:
template <class Archive>
void save(Archive& archive, const NotSet& object)
{
}
template <class Archive>
void load(Archive& archive, NotSet& object)
{
}
I am guessing that -O1
noticed some empty void functions and optimized something away. Then somebody else tried to link to libIO.so
and found the symbols missing. I was able to fix this by adding a dummy field to NotSet
and then serializing that field.
Now it builds successfully with -O2
but -O3
causes the following:
../lib/libIO.so: undefined reference to `void save<cereal::JSONOutputArchive>(cereal::JSONOutputArchive&, Emissions const&)'
../lib/libIO.so: undefined reference to `void save<cereal::JSONOutputArchive>(cereal::JSONOutputArchive&, VehicleBound const&)'
Which is similar to the original error, but the reference to NotSet
is not present. I've been over the two calls that are causing errors repeatedly--they look identical to all of the other well-behaved calls.
It seems likely to be related to this: c++ linker error: undefined references only on optimized build because the troublesome calls are function templates. However, their definitions are in a header which is included in a file, which is specified to be built as part of libIO.so.