C++ Logo

std-discussion

Advanced search

Re: Global array of objects over multiple files

From: Thiago Macieira <thiago_at_[hidden]>
Date: Sun, 20 Oct 2024 09:03:45 -0700
On Saturday 19 October 2024 23:18:33 GMT-7 Federico Kircheis via Std-
Discussion wrote:
> On 20/10/2024 07.58, Thiago Macieira via Std-Discussion wrote:
> > On Saturday 19 October 2024 12:59:31 GMT-7 Federico Kircheis via Std-
> >
> > Discussion wrote:
> >> As an example, a class that logs in the destructor it's address and a
> >> string:
> >>
> >> https://godbolt.org/z/sb8n8fn1a
> >
> > This one has a ODR violation: the symbol "instance" is defined in two TUs.
> > It's ill-formed.
>
> AFAIK it is not an ODR violation.
> There is one definition and multiple declarations, it is not UB.

That's not how headers work. You have a full definition in the .hpp:

const my_struct instance;

So if you include this header in two TUs and they both end up in your
executable at runtime, it's an ODR violation.

> >> Same class, slightly different context:
> >>
> >> https://godbolt.org/z/Thv8xhc6Y
> >
> > Also ODR violation for the same reason.
>
> In this cases, there is one definition and one declaration in one TU.

See below because it's the same case.

> >> Another example that shows the same behavior
> >>
> >> https://godbolt.org/z/ecPPq8vns
> >
> > This is the exact same code as the previous one: lib0.cpp is linked twice
> > into your executable, therefore ODR violation.
>
> lib0.cpp is linked once in the dynamic library lib1, once in the dynamic
> library in lib2, and 0 times in main (and main uses lib1 and lib2).
> lib0 has only one declaration.

The problem is what happens when you load the application for runtime: then
lib1 and lib2 are both in memory at the same time, working as if "they were
another TU". Therefore, their both including lib0.cpp implies it's an ODR
violation.


> If lib1,lib2 where applications and main would communicate through IPC,
> then there would be no issues.

Yes, but if wishes were fishes don't count. They're not.

> If lib1 and lib2 where static libraries (or to put it another way, if
> they where not separate libraries), there would be no issue, since there
> is only one declaration in the whole code basis.

Again, if you change what is to something different, the problem changes too.
Static libs don't link. What you get is CMake tracking that lib1 and lib2 have
a dependency on lib0, so when you link your final executable, your final
executable links directly to lib0.

If you want to reproduce this problem, you first need to change both lib1 and
lib2 so they incorporate lib0 into their .a. That simulates what happens
during dynamic linking (roughly enough, but works for this case) meaning the
contents of lib0.a get emitted inside of lib1.a and lib2.a. Then, you must
link both of those libs into your executable as -Wl,--whole-archive, because
this mimics what happens at runtime: the contents of the entire dynamic
library become visible.

Doing this means you have two copies of lib0.cpp.o into the executable. This
is an ODR violation and thus IFNDR. And in this case, a diagnostic *would* be
emitted, because the static linker can tell that some symbols were defined
twice.

> But even if it where an ODE, how do you fix those issue with dynamic
> libraries?

Emit a single copy of "instance" in exactly one library. Which one you choose
to do that is up to you.

> Assuming that the author of lib0 is not the same of lib1 and lib2, who
> needs to change what?

You chose to use lib1 and lib2. You can choose not to, if they don't react to
your bug reports. Vote with your feet and your wallet.

Usually, the problem here is that of vendoring other content. It's fine to
vendor once (for some definitions of "fine"). It's not fine for that to happen
twice. So if two modules do it, you can't use them both in the same
executable.

You can work around the problem here by hiding those symbols from lib0
completely inside of each of the two shared libraries. This is where you must
step outside of the Standard and use compiler and platform-specific extensions,
like hidden visibility for the entirety of lib0.

The practice of vendoring must stop. At a minimum, vendored content must be
unbundleable, if nothing else for fixing security issues.

> In all the examples, the global variable is an implementation detail, it
> is not even used directly outside of lib0.

Neither the compiler nor the Standard care about that.

-- 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
  Principal Engineer - Intel DCAI Platform & System Engineering

Received on 2024-10-20 16:03:50