I am probably mistaken about how dynamic linking works, because I cannot figure this out. As I understood it, when a library is dynamically linked, its symbols are resolved at runtime. From this answer:
When you link dynamically, a pointer to the file being linked in (the file name of the file, for example) is included in the executable and the contents of said file are not included at link time. It's only when you later run the executable that these dynamically linked files are bought in and they're only bought into the in-memory copy of the executable, not the one on disk.
[...]
In the dynamic case, the main program is linked with the C runtime import library (something which declares what's in the dynamic library but doesn't actually define it). This allows the linker to link even though the actual code is missing.
Then, at runtime, the operating system loader does a late linking of the main program with the C runtime DLL (dynamic link library or shared library or other nomenclature).
I am confused as to why g++
seems to expect the shared object to be there when dynamically linking against it. Sure, I would expect the name of the library to be necessary so that it can be loaded at runtime, but why is it the .so
necessary at this stage? Furthermore, g++
complains about undefined references when linking against the library.
My questions are:
- Why does
g++
seem to require the shared object when dynamically linking against it if the loading of the library only happens at runtime? I understand how the-l
flag could be necessary to specify the name of the shared object so that it can be loaded in runtime, but I see no point in having to provide the path to the.so
at link time (-L
) or the.so
itself. - Why does
g++
attempt to resolve the symbols when dynamically linking? Nothing stops me from having a complete.so
at link time but then providing a different (incomplete).so
at runtime, which causes the program to crash when it tries to use an undefined symbol.
I made a reproducible example:
Directory structure:
.
├── main.cpp
└── test
├── usertest.cpp
└── usertest.h
File contents:
test/usertest.h
#ifndef USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
#define USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346
namespace usertest
{
void helloWorld();
// This method is not defined anywhere
void byeWorld();
};
#endif /* USERTEST_H_4AD3C656_8109_11E8_BED5_5BE6E678B346 */
test/usertest.cpp
#include "usertest.h"
#include <iostream>
void usertest::helloWorld()
{
std::cout << "Hello, world\n";
}
main.cpp
#include "test/usertest.h"
int main()
{
usertest::helloWorld();
usertest::byeWorld();
}
Usage
$ cd test
$ g++ -c -fPIC usertest.cpp
$ g++ usertest.o -shared -o libusertest.so
$ cd ..
$ g++ main.cpp -L test/ -lusertest
$ LD_LIBRARY_PATH="test" ./a.out
Expected behaviour
I would expect everything to crash when attempting to launch a.out
because it cannot find the necessary symbols in libusertest.so
.
Actual behaviour
The building of a.out
fails at link time because it cannot find byeWorld()
:
/tmp/ccVNcRRY.o: In function `main':
main.cpp:(.text+0xa): undefined reference to `usertest::byeWorld()'
collect2: error: ld returned 1 exit status
.so
are unknown. But I'm not sure. – Merridie.so
, andg++
gets ahold of them via the-I
flag, so it can't be that :/ Also, headers are involved at compile time, not link time. – Gulick