1

I am using CMake to generate some build environments:

  1. cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Debug -DBUILD_SHARED_LIBS=TRUE

  2. cmake .. -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=TRUE

  3. 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.

starball
  • 20,030
  • 7
  • 43
  • 238
MatrixManAtYrService
  • 8,023
  • 1
  • 50
  • 61
  • 1
    it's common to check the build type (debug / release) in code via #ifdef and include or exclude code based on that. So it's very easy to make this happen. Whether that's what is happening here or not I have no idea. – Erix Feb 21 '17 at 00:38
  • @Erix That's a good thought. I looked for relevant `ifdef`s and am sorry to say I didn't find any. – MatrixManAtYrService Feb 21 '17 at 00:44
  • 1
    Also check for use of the `assert` macro. Sometimes important code works its way into an assert, and when the assert goes away the code goes away, too. – Pete Becker Feb 21 '17 at 00:51
  • @PeteBecker I found many asserts, and learned that I can prevent the removal of asserts in release mode by *not* passing `-DNDEBUG`. It didn't fix the problem, but you've certainly set me on the right path--fiddling with those parameters proved to be instructive (see edit 2). Thank you. – MatrixManAtYrService Feb 21 '17 at 03:51
  • 1
    Have you put your definitions of `save` and `load` in a header file or in a .cpp file? If the former, does every .cpp file that potentially uses these definitions #include this header? – n. m. could be an AI Feb 21 '17 at 05:59
  • 4
    You probably have some code that relies on other code to instantiate your `load` and `save` with whatever `Archive` it needs. What happens is that other code inlines all instantiations when optimisations are on. You need to either (a) stop relying on other code to instantiate stuff (#include the header with your definitions everywhere these functions are needed); or (b) instantiate whatever is needed explicitly. – n. m. could be an AI Feb 21 '17 at 06:14

0 Answers0