Resolving circular dependencies by linking the same library twice?

The problem with

g++ -o myApp -lfoo -lbar -lfoo

is that there is no guarantee, that two passes over libfoo and one pass over libbar are enough.

The approach with Wl,--start-group ... -Wl,--end-group is better, because more robust.

Consider the following scenario (all symbols are in different object-files):

  • myApp needs symbol fooA defined in libfoo.
  • Symbol fooA needs symbol barB defined in libbar.
  • Symbol barB needs symbol fooC defined in libfoo. This is the circular dependency, which can be handled by -lfoo -lbar -lfoo.
  • Symbol fooC needs symbol barD defined in libbar.

To be able to build in the case above, we would need to pass -lfoo -lbar -lfoo -lbar to the linker. Why?

  1. The linker sees libfoo for the first time and uses definitions of symbol fooA but not fooC, because so far it doesn’t see a necessity to include also fooC into the binary. The linker however starts to look for definition of barB, because its definition is needed for fooA to function.
  2. The linker sees -libbar, includes the definition of barB (but not barD) and starts to look for definition of fooC.
  3. The definition of fooC is found in libfoo, when it processed for the second time. Now it becomes evident, that also the definition of barD is needed – but too late there is no libbar on the command line anymore!

The example above can be extended to an arbitrary dependency depth (but this happens seldom in real life).

Thus using

g++ -o myApp -Wl,--start-group -lfoo -lbar -Wl,--end-group

is a more robust approach, because linker passes as often over the library group as needed – only when a pass didn’t change the symbol table will the linker move on to the the next library on the command line.

There is however a small performance penalty to pay: in the first example -lbar were scanned once more compared with the manual command line -lfoo -lbar -lfoo. Not sure whether it is worth mentioning/thinking about through.

Leave a Comment