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)