Using GCC 11
,
gcc --version
gcc (GCC) 11.3.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
I'm building a library libsimplemath.a
, based on two objects (add.o
and sub.o
), sub.o
has an external dependency on undef_symb
:
// add.cpp
int add(int a, int b) { return a + b; }
// sub.cpp
#include <string>
void undef_symb(void);
int sub(int a, int b)
{
std::string s;
undef_symb();
return a - b;
}
g++ -O0 -std=c++20 -o sub.o -c sub.cpp
g++ -O0 -std=c++20 -o add.o -c add.cpp
ar rc libsimplemath.a add.o sub.o
Let's say one of the libsimplemath.a
user has a main only depending on add
interface:
// main.cpp
#include <string>
int add(int, int);
int main()
{
std::string s = "Hello";
return add(0, 0);
}
When the code is compiled using c++20 flag, everything works fine:
g++ -O0 -std=c++20 -o main.o -c main.cpp
g++ -o main main.o -L. -lsimplemath
No problem.
However when the code is compiled using c++17 flag (and this is the current user flow), it fails at link time:
g++ -O0 -std=c++17 -o main.o -c main.cpp
g++ -o main main.o -L. -lsimplemath
/arm/tools/gnu/binutils/2.32/rhe7-x86_64/bin/ld: ./libsimplemath.a(sub.o): in function `sub(int, int)':
sub.cpp:(.text+0x1c): undefined reference to `undef_symb()'
collect2: error: ld returned 1 exit status
Here's a CMake project @ compiler explorer that reproduces the problem.
For some reasons sub.o
is also brought by linker and, as it contains an undefined symbol, linking is failing.
To know why sub.o
is taken into account linker map file can be looked at (-Wl,-Map=main.map
linker option):
Archive member included to satisfy reference by file (symbol)
./libsimplemath.a(add.o) main.o (add(int, int))
./libsimplemath.a(sub.o) main.o (std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string())
basic_string
stl symbol is resolved in sub.o
.
By looking at nm
output we can see that basic_string
constructor is not defined in main.o
:
nm -aC main.o | grep basic_string
U std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()
It is defined in sub.o
:
nm -aC sub.o | grep basic_string\(
0000000000000000 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()
It explains why sub.o
is brought by linker, and the fail.
The fact that basic_string
symbols are defined or not in the object file depends on the -std=c++XX
flag used. Compiling main.cpp
with -std=c++20
:
nm -aC main.o | grep basic_string\(
0000000000000000 W std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string()
The symbol is defined, no need to resolve it elsewhere, thus sub.o
is not brought and linking works.
The reason why basic_string
is defined or not seems to come from the fact that it's defined as extern or not:
in include/c++/11.3.0/bits/basic_string.tcc
// Inhibit implicit instantiations for required instantiations,
// which are defined via explicit instantiations elsewhere.
#if _GLIBCXX_EXTERN_TEMPLATE
// The explicit instantiation definitions in src/c++11/string-inst.cc and
// src/c++17/string-inst.cc only instantiate the members required for C++17
// and earlier standards (so not C++20's starts_with and ends_with).
// Suppress the explicit instantiation declarations for C++20, so C++20
// code will implicitly instantiate std::string and std::wstring as needed.
# if __cplusplus <= 201703L && _GLIBCXX_EXTERN_TEMPLATE > 0
extern template class basic_string<char>;
For std=c++17
the template is extern
and thus basic_string
is not defined in main.o
.
So this use-case seems to break the interoperability between different standard (here c++17
and c++20
). Is it expected?
If there is no problem here then it means every single dependency must be provided at link time? Even the ones the user (main writer) doesn't care of? (I didn't find resources claiming it)