Some modified files to demonstrate some points:
CMakeLists.txt
cmake_minimum_required(VERSION 3.0)
project(Test)
#add_library(Test SHARED test1.cpp test2.cpp)
add_library(test STATIC test1.cpp test2.cpp test3.cpp)
test1.h
#pragma once
int test();
test1.cpp
#include "test1.h"
int myvar1 = 12345;
int test()
{
return myvar1;
}
test2.h
#pragma once
int test();
test2.cpp
#include "test2.h"
int myvar2=22;
int test()
{
return myvar2;
}
test3.h
#pragma once
int test();
int test3();
test3.cpp
#include "test3.h"
int myvar3=333;
int test()
{
return myvar3;
}
int test3()
{
return 3332;
}
build.sh
tree
cat ./*
cmake .
echo "build the library"
make VERBOSE=1
echo "show symbols"
nm ./libtest.a | c++filt
echo "build the executable manually"
g++ -c ./main.cpp -o main.o
echo "link manually"
g++ --verbose -Wl,--warn-common -Wl,--verbose ./main.o ./libtest.a -o testexec
./testexec
echo "clean"
rm -r ./CMakeCache.txt ./*.cmake ./Makefile ./CMakeFiles ./testexec ./*.o ./*.a ./*.so
A test build
A redacted [...] output of the build script is as follows:
.
├── build.sh
├── CMakeLists.txt
├── main.cpp
├── test1.cpp
├── test1.h
├── test2.cpp
├── test2.h
├── test3.cpp
└── test3.h
0 directories, 11 files
@cmake
[...]
@build the library
[...]
[ 25%] Building CXX object CMakeFiles/test.dir/test1.cpp.o
/usr/bin/c++ -MD -MT CMakeFiles/test.dir/test1.cpp.o -MF CMakeFiles/test.dir/test1.cpp.o.d -o CMakeFiles/test.dir/test1.cpp.o -c /tmp/cpp/test1.cpp
[ 50%] Building CXX object CMakeFiles/test.dir/test2.cpp.o
/usr/bin/c++ -MD -MT CMakeFiles/test.dir/test2.cpp.o -MF CMakeFiles/test.dir/test2.cpp.o.d -o CMakeFiles/test.dir/test2.cpp.o -c /tmp/cpp/test2.cpp
[ 75%] Building CXX object CMakeFiles/test.dir/test3.cpp.o
/usr/bin/c++ -MD -MT CMakeFiles/test.dir/test3.cpp.o -MF CMakeFiles/test.dir/test3.cpp.o.d -o CMakeFiles/test.dir/test3.cpp.o -c /tmp/cpp/test3.cpp
[100%] Linking CXX static library libtest.a
/usr/bin/cmake -P CMakeFiles/test.dir/cmake_clean_target.cmake
/usr/bin/cmake -E cmake_link_script CMakeFiles/test.dir/link.txt --verbose=1
/usr/bin/ar qc libtest.a CMakeFiles/test.dir/test1.cpp.o CMakeFiles/test.dir/test2.cpp.o CMakeFiles/test.dir/test3.cpp.o
/usr/bin/ranlib libtest.a
[...]
@show symbols
test1.cpp.o:
0000000000000000 D myvar1
0000000000000000 T test()
test2.cpp.o:
0000000000000000 D myvar2
0000000000000000 T test()
test3.cpp.o:
0000000000000000 D myvar3
0000000000000000 T test()
000000000000000c T test3()
@build the executable manually
@link manually and run
Using built-in specs.
[...]
/usr/lib64/gcc/x86_64-suse-linux/7/crtbegin.o
attempt to open ./main.o succeeded
./main.o
attempt to open ./libtest.a succeeded
./libtest.a
(./libtest.a)test1.cpp.o
(./libtest.a)test3.cpp.o
attempt to open /usr/lib64/gcc/x86_64-suse-linux/7/libstdc++.so succeeded
[...]
ld-linux-x86-64.so.2 needed by /usr/lib64/gcc/x86_64-suse-linux/7/libstdc++.so
found ld-linux-x86-64.so.2 at /lib64/ld-linux-x86-64.so.2/usr/lib64/gcc/x86_64-suse-linux/7/../../../../x86_64-suse-linux/bin/ld: ./libtest.a(test3.cpp.o): in function `test()':
test3.cpp:(.text+0x0): multiple definition of `test()'; ./libtest.a(test1.cpp.o):test1.cpp:(.text+0x0): first defined here
/usr/lib64/gcc/x86_64-suse-linux/7/../../../../x86_64-suse-linux/bin/ld: link errors found, deleting executable `testexec'
collect2: error: ld returned 1 exit status
@clean
Explanation
cmake invokes ar (archiver) to create a static library (which is the
standard way to do it). However, ar is unaware of program symbols and
blindly packages .o (object) files.
ld (the GNU linker) has "smart" symbol detection when loading external
libraries. This answer has many interesting details
In the static compile shown above, libtest.a is treated as an external
library and linking and loading is done per the following logic:
- ld processes objects from left to right in its command. Swapping
main.o and libtest.a in the command fails differently.
- ld resolves the test() symbol in main.cpp.o to test() in test1.cpp.o
- ld detects that it has no more symbols to resolve which are available
in test2.cpp.o so test2.cpp.o is never even loaded by ld.
- ld resolves the test3() symbol in main.cpp.o to test3() in
test3.cpp.o so test3.cpp.o is loaded.
However, test3.cpp.o also contains the test() symbol so the
conflicting symbol error occurs just like when linking dynamically /
a shared library.
Removing or renaming test() in test3.cpp resolves this.