Why Isn't the Dynamic Link Library Using Its Statically Linked Functions?

4 weeks ago 27
ARTICLE AD BOX
a (main calls c and d) | +-----------+-----------+ | | libc.so (c calls b) libd.so (d calls b) | | | | libb1.a libb2.a

In b{1,2}.cpp, I have defined 2 different versions of a function named b (with the same identifier).

I expected that when the functions {c,d} in lib{c,d}.so call b, they would invoke the version that was statically linked to themselves respectively.
However, the result was unexpected: at runtime, it always behaves as if only one single version of b exists.


My question is: Is this behavior deterministic? Regardless of how many duplicate definitions of the same name exist, and regardless of the linking method used (dynamic, static, etc.), will only one of them ever be picked at runtime? Do the Linker and Loader always guarantee this behavior?

(My apologies for my lack of knowledge regarding these low-level details.)


Below is the concise test code:

headers:

// b.h #include <string> std::string b(const std::string&); // c.h #include <string> std::string c(); // d.h #include <string> std::string d();

sources:

// a.cpp #include <iostream> #include "c.h" #include "d.h" int main() {std::cout << c() << '\n' << d() << '\n';} // b.cpp #include <format> #include <string> std::string b(const std::string& v) { static const auto s = v; return std::format("B{}({})", VERSION, s); } // c.cpp #include <format> #include <string> #include "b.h" std::string c() {return std::format("C({},*{})", b("c"), (void *)b);} // d.cpp #include <format> #include <string> #include "b.h" std::string d() {return std::format("D({},*{})", b("d"), (void *)b);}

CML:

cmake_minimum_required(VERSION 3.23) project(A) add_executable(a) target_sources(a PRIVATE a.cpp) target_link_libraries(a PRIVATE c d) add_library(b1 STATIC) target_sources(b1 PRIVATE b.cpp INTERFACE FILE_SET HEADERS ) target_compile_definitions(b1 PRIVATE VERSION=1) set_target_properties(b1 PROPERTIES POSITION_INDEPENDENT_CODE ON) add_library(b2 STATIC) target_sources(b2 PRIVATE b.cpp INTERFACE FILE_SET HEADERS ) target_compile_definitions(b2 PRIVATE VERSION=2) set_target_properties(b2 PROPERTIES POSITION_INDEPENDENT_CODE ON) add_library(c SHARED) target_sources(c PRIVATE c.cpp INTERFACE FILE_SET HEADERS ) target_link_libraries(c PRIVATE b1) add_library(d SHARED) target_sources(d PRIVATE d.cpp INTERFACE FILE_SET HEADERS ) target_link_libraries(d PRIVATE b2)

Compile and execute:

$ rm -rf build $ cmake -DCMAKE_CXX_STANDARD=20 -B build $ cmake --build build -j $ ./build/a C(B1(c),*0x7dca4a1ec8bf) D(B1(c),*0x7dca4a1ec8bf) # I expect: # C(B1(c),*0xABCD111) # D(B2(d),*0xABCD222)
Read Entire Article