3.11.4.3. ODR violation (local)

Examples on GitHub

The next example is about scenario when badly written CMake code leads to ODR violation.

Assume we have library boo:

# boo/CMakeLists.txt

add_definitions(-DBOO_USE_SHORT_INT) # This is wrong!
add_library(boo Boo.hpp Boo.cpp)
// boo/Boo.hpp

#ifndef BOO_HPP_
#define BOO_HPP_

class Boo {
 public:
#ifdef BOO_USE_SHORT_INT
  typedef short int value_type;
#else
  typedef unsigned long long value_type;
#endif

  static void boo(int, value_type);
};

#endif // BOO_HPP_
// boo/Boo.cpp

#include "boo/Boo.hpp"

void Boo::boo(int, value_type) {
}

Methods of boo used in library foo:

// foo/Foo.hpp

#ifndef FOO_HPP_
#define FOO_HPP_

class Foo {
 public:
  static void foo(int, int);
};

#endif // FOO_HPP_
// foo/Foo.cpp

#include "foo/Foo.hpp"
#include "boo/Boo.hpp"

void Foo::foo(int, int) {
  Boo::value_type x(2);
  return Boo::boo(1, x);
}
# foo/CMakeLists.txt

add_library(foo Foo.hpp Foo.cpp)
target_link_libraries(foo PUBLIC boo)

And final executable baz:

# Top-level CMakeLists.txt

cmake_minimum_required(VERSION 2.8)
project(baz)

include_directories(${CMAKE_CURRENT_LIST_DIR}) # for '#include <boo/Boo.hpp>'

add_subdirectory(boo)
add_subdirectory(foo)

add_executable(baz main.cpp)
target_link_libraries(baz foo)
#include "foo/Foo.hpp"

int main() {
  Foo::foo(0, 0);
}

Let’s build the project now:

[library-examples]> rm -rf _builds
[library-examples]> cmake -Hlink-error-odr-local -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
...
[library-examples]> cmake --build _builds

Link will fail with “undefined reference” error:

/usr/bin/c++ -DBOO_USE_SHORT_INT /.../Boo.cpp
...
/usr/bin/c++ /.../Foo.cpp
...
/usr/bin/c++ -rdynamic CMakeFiles/baz.dir/main.cpp.o -o baz foo/libfoo.a boo/libboo.a
foo/libfoo.a(Foo.cpp.o): In function `Foo::foo(int, int)':
Foo.cpp:(.text+0x23): undefined reference to `Boo::boo(int, unsigned long long)'
collect2: error: ld returned 1 exit status
CMakeFiles/baz.dir/build.make:99: recipe for target 'baz' failed
make[2]: *** [baz] Error 1

Check symbols we need:

[library-examples]> nm --defined-only --demangle _builds/boo/libboo.a

Boo.cpp.o:
0000000000000000 T Boo::boo(int, short)

Indeed that’s not what we are looking for:

[library-examples]> nm --undefined-only --demangle _builds/foo/libfoo.a

Foo.cpp.o:
    U Boo::boo(int, unsigned long long)

The reason of the failure is that we use BOO_USE_SHORT_INT while building boo library and not using it while building library foo. Since in both cases we are loading boo/Boo.hpp header (which depends on BOO_USE_SHORT_INT) we should define BOO_USE_SHORT_INT in both cases too. target_compile_definitions can help us to solve the issue:

--- /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/examples/library-examples/link-error-odr-local/boo/CMakeLists.txt
+++ /home/docs/checkouts/readthedocs.org/user_builds/cgold/checkouts/latest/docs/examples/library-examples/link-error-odr-local-fix/boo/CMakeLists.txt
@@ -1,4 +1,4 @@
 # boo/CMakeLists.txt
 
-add_definitions(-DBOO_USE_SHORT_INT) # This is wrong!
 add_library(boo Boo.hpp Boo.cpp)
+target_compile_definitions(boo PUBLIC "BOO_USE_SHORT_INT")

Links fine now:

[library-examples]> rm -rf _builds
[library-examples]> cmake -Hlink-error-odr-local-fix -B_builds -DCMAKE_VERBOSE_MAKEFILE=ON
...
[library-examples]> cmake --build _builds
/usr/bin/c++ -DBOO_USE_SHORT_INT /.../Boo.cpp
...
/usr/bin/c++ -DBOO_USE_SHORT_INT /.../Foo.cpp
...
/usr/bin/c++ -DBOO_USE_SHORT_INT /.../main.cpp
...
/usr/bin/c++ -rdynamic CMakeFiles/baz.dir/main.cpp.o -o baz foo/libfoo.a boo/libboo.a
...
> nm --defined-only --demangle _builds/boo/libboo.a
Boo.cpp.o:
0000000000000000 T Boo::boo(int, short)
> nm --undefined-only --demangle _builds/foo/libfoo.a
Foo.cpp.o:
    U Boo::boo(int, short)