We have a code base broken up into static libraries. Unfortunately, the libraries have circular dependencies; e.g., libfoo.a
depends on libbar.a
and vice-versa.
I know the "correct" way to handle this is to use the linker's --start-group
and --end-group
options, like so:
g++ -o myApp -Wl,--start-group -lfoo -lbar -Wl,--end-group
But in our existing Makefiles, the problem is typically handled like this:
g++ -o myApp -lfoo -lbar -lfoo
(Imagine this extended to ~20 libraries with complex interdependencies.)
I have been going through our Makefiles changing the second form to the first, but now my co-workers are asking me why... And other than "because it's cleaner" and a vague sense that the other form is risky, I do not have a good answer.
So, can linking the same library multiple times ever create a problem? For example, could the link fail with multiply-defined symbols if the same .o gets pulled in twice? Or is there any risk we could wind up with two copies of the same static object, creating subtle bugs?
Basically, I want to know if there is any possibility of link-time or run-time failures from linking the same library multiple times; and if so, how to trigger them. Thanks.
There are a couple of options to get rid of circular dependencies. For a longer chain, A -> B -> C -> D -> A , if one of the references is removed (for instance, the D -> A reference), the cyclic reference pattern is broken, as well. For simpler patterns, such as A -> B -> A , refactoring may be necessary.
A cyclic dependency is an indication of a design or modeling problem in your software. Although you can construct your object graph by using property injection, you will ignore the root cause and add another problem: property injection causes Temporal Coupling. Instead, the solution is to look at the design closely.
A circular dependency is detected whenever two objects reference each other, in such a way that Power BI cannot process the objects. The details of why this happens are outlined in this article: Understanding Circular Dependencies in Tabular and PowerPivot – SQLBI.
4.2. A simple way to break the cycle is by telling Spring to initialize one of the beans lazily. So, instead of fully initializing the bean, it will create a proxy to inject it into the other bean. The injected bean will only be fully created when it's first needed.
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
.fooA
needs symbol barB
defined in libbar
.barB
needs symbol fooC
defined in libfoo
. This is the circular dependency, which can be handled by -lfoo -lbar -lfoo
.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?
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.-libbar
, includes the definition of barB
(but not barD
) and starts to look for definition of fooC
.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.
All I can offer is a lack of counter-example. I've actually never seen the first form before (even though it's clearly better) and always seen this solved with the second form, and haven't observed problems as a result.
Even so I would still suggest changing to the first form because it clearly shows the relationship between the libraries rather than relying on the linker behaving in a particular way.
That said, I would suggest at least considering if there's a possibility of refactoring the code to pull out the common pieces into additional libraries.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With