Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Last active August 29, 2019 04:46
Show Gist options
  • Save ChunMinChang/553962a80ac4d873454597d82227c276 to your computer and use it in GitHub Desktop.
Save ChunMinChang/553962a80ac4d873454597d82227c276 to your computer and use it in GitHub Desktop.
A counter examples for writing a share library

Duplicate symbol when compiling

We will have duplicate symbol for architecture x86_64 when we compile the programs as follows:

$ g++ -c -Wall bye.cpp   # Generate bye.o
$ g++ -c -Wall hello.cpp # Generate hello.o
$ g++ -Wall main.cpp bye.o hello.o -o run
duplicate symbol __Z3LOGNSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE in:
    bye.o
    hello.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [all] Error 1

The reason is that we include the shared header(utils.h) into different files(bye.cpp and hello.cpp), and compile those files into different libraries(bye.o and hello.o), so the functions in the shared header(LOG) duplicate in those different libraries.

Thus, when we try using those different libraries(bye.o and hello.o) at the same time, there are duplicated symbols for functions(LOG) included from the shared header(utils.h). The program has no idea about which one it should call among those duplicated symbols.

Solution 1: Using macros instead of functions

The macro is only textual substitution that expanded by the preprocessor, so there is no symbol generated.

You can replace function LOG by

#define LOG(s) (std::cout << s << std::endl)

Then the SayBye() will be expanded into:

void SayBye()
{
  (std::cout << "Goodbye" << std::endl);
}

You can run: $ g++ -E <file_name>.cpp to watch and confirm the preprocessor's output.

Solution 2: Make functions inline

It works almost same as macro. Inline functions are actual functions whose copy of the function body are injected directly into each place the function is called.

inline void LOG(std::string s)
{
  std::cout << s << std::endl;
}

The insertion occurs only if the compiler's cost/benefit analysis shows it to be profitable. Same as the macros, inline expansion eliminates the overhead associated with function calls.

Inline functions are parsed by the compiler, whereas macros are expanded by the preprocessor. The preprocessor macros are just substitution patterns in code before the compilation, so there is no type-checking at that time. While inline functions are actual functions, so compiler can keep an eye on type-checking issues to help debugging.

See here for more details.

Solution 3: Using static to make functions local in each file

Since their states are not sharable, they should not visible across each other. Thus, the generated symbols are also local in each file.

static void LOG(std::string s)
{
  std::cout << s << std::endl;
}
#include "bye.h"
#include "utils.h"
void SayBye()
{
LOG("Goodbye");
}
#ifndef BYE_H
#define BYE_H
void SayBye();
#endif /* BYE_H */
#include "hello.h"
#include "utils.h"
void SayHello()
{
LOG("Hello");
}
#ifndef HELLO_H
#define HELLO_H
void SayHello();
#endif /* HELLO_H */
#include "bye.h"
#include "hello.h"
int main()
{
SayHello();
SayBye();
return 0;
}
CXX=g++
CFLAGS=-Wall
EXEC=run
all: bye.o hello.o
$(CXX) $(CFLAGS) main.cpp bye.o hello.o -o $(EXEC)
bye.o: bye.cpp
$(CXX) -c $(CFLAGS) bye.cpp
hello.o: hello.cpp
$(CXX) -c $(CFLAGS) hello.cpp
clean:
rm $(EXEC) *.o
#ifndef UTILS_H
#define UTILS_H
#include <iostream>
#include <string>
void LOG(std::string s)
{
std::cout << s << std::endl;
}
#endif /* UTILS_H */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment