Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can reordering the linked libraries fix multiple definitions error?

I faced a situation where different order of linking librdkafka and the Pulsar C++ client does matter, because both of them include their version of LZ4 compression. The linking fails because of multiple definitions of LZ4 functions (both librdkafka and Pulsar have the same names for those functions). I checked the static libraries, but I couldn't find anything suspicious, why it works in one order and doesn't in the another. Because it is hard to provide a minimal working example with those big libraries, I tried to reproduce the same situation, and I was able to do so. I created a small project where the linking order matters.

libA.hpp:

#pragma once

void NotClashingFunctionA();
void ClashingFunction();

libA.cpp:

#include "libB.hpp"

#include <iostream>

void NotClashingFunctionA() {
    std::cout << "Not clashing function A\n";
}

void ClashingFunction() {
    std::cout << "Clashing function A\n";
}

libB.hpp:

#pragma once

void NotClashingFunctionB();

libB.cpp:

#include "libB.hpp"

#include <iostream>

void NotClashingFunctionB() {
    std::cout << "Not clashing function B\n";
}

libBSub.hpp:

#pragma once

void ClashingFunction();

libBSub.cpp:

#include "libBSub.hpp"

#include <iostream>

void ClashingFunction() {
    std::cout << "Clashing function B\n";
}

main.cpp:

#include "libA.hpp"
#include "libB.hpp"
#include "libBSub.hpp"

int main() {
    NotClashingFunctionA();
    NotClashingFunctionB();
    ClashingFunction();
    return 0;
}

CMakeLists.txt:

project(clashing)

add_library(A STATIC libA.cpp)

add_library(B STATIC libB.cpp libBSub.cpp)

add_executable(working main.cpp)
target_link_libraries(working A B)

add_executable(failing main.cpp)
target_link_libraries(failing B A)

From the logs I can clearly see that working just links fine:

clang++ -g -rdynamic CMakeFiles/working.dir/main.cpp.o -o working  libA.a libB.a 
make[3]: Leaving directory 'build'
[100%] Built target working

But failing fails to link:

clang++ -g -rdynamic CMakeFiles/failing.dir/main.cpp.o -o failing  libB.a libA.a 
ld: libA.a(libA.cpp.o): in function `ClashingFunction()':
libA.cpp:9: multiple definition of `ClashingFunction()'; libB.a(libBSub.cpp.o):libBSub.cpp:5: first defined here
clang-12: error: linker command failed with exit code 1 (use -v to see invocation)

I removed the common prefixes to make the logs more readable.

As you can see the only difference between the two is the linking order A and B. I don't know why it works in A B order, and not B A order.

If you cannot explain it in details, helping keywords are also very appreciated, because I have absolutely no idea why it is happening.

like image 736
János Benjamin Antal Avatar asked Nov 07 '22 00:11

János Benjamin Antal


1 Answers

To understand why, read this (earlier) post or this (nicer) one.

To make a concrete example:

  • suppose main.o defines main(), fn(), and references a() and b().
  • libA.a contains a.o which defines a()
  • libB.a contains b.o which defines b(), and also a1.o which defines a() and fn().

Now, if you link with gcc main.o -lA -lB, the link will succeed (a.o from libA.a and b.o from libB.a will be selected into the link, and no symbol conflicts will arise. Notably, a1.o from libB.a will not be selected into the link).

But if you link with gcc main.o -lB -lA, then fn() will be multiply defined (because both a1.o and b.o from libB.a will be selected into the link, but the definition of fn() in a1.o will be in conflict with the definition of fn() in main.o).

like image 163
Employed Russian Avatar answered Nov 14 '22 03:11

Employed Russian