Are inline variables unique across boundaries?
Asked Answered
D

3

25

This is a follow up of this question.
As mentioned in the comments to the answer:

An inline variable has the property that - It has the same address in every translation unit. [...] Usually you achieved that by defining the variable in a cpp file, but with the inline specifier you can just declare/define your variables in a header file and every translation unit using this inline variable uses exactly the same object.

Moreover, from the answer itself:

While the language does not guarantee (or even mention) what happens when you use this new feature across shared libraries boundaries, it does work on my machine.

In other terms, it isn't clear if an inline variable is guaranteed to be unique across boundaries when shared libraries are involved. Someone proved empirically that it works on some platforms, but it isn't properly an answer and it could just break everything on other platforms.

Is there any guarantee regarding the uniqueness of an inline variable when it is used across boundaries or is it simply an implementation detail that I should not rely on?

Dead answered 17/7, 2018 at 6:27 Comment(8)
The C++ language Standard simply isn't aware of any concept such as "shared libraries". So this is a guarantee that would have to be provided by your platform.Keiko
@Keiko It looks like an elaborate way to say - no, it isn't. :-) If you can put it in an answer and add a few extra details, it would be appreciated. Thank you.Dead
@Dead Every linker ensure uniqueness of inline variable otherwise the implementation is not conforming. The problem with inline variable is exactly the same as the one of a static variables which are local to an inline function. The problem of the definition uniqueness of theses local static variables has been solved a long time ago. This is refered as vague linkage hereAllegro
Here the orignal paper from a very reliable author explains the same thing: open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4424.pdfAllegro
@Allegro I think things messed a bit up in your link. :-)Dead
@Oliv: Updated my answer in response to the first link you posted. Thank you for bringing it up.Poorly
@Allegro "Linker" here is ambiguous however when shared libraries are involved. The dynamic (aka run time) linker may not be as powerful as the linker used to build the shared libraries, or symbol resolution may be intentionally restricted (e.g. when you specify the RTLD_PRIVATE flag to dlopen on Linux). I have a pretty good understanding of various factors on Linux, so probably should write an answer…Landon
@ArneVogel If you could do it, it would be very instructive, at least for me.Allegro
E
10

This is how I interpret the standard. According to basic.link/1:

A program consists of one or more translation units linked together.

It doesn't say anything about static linking nor dynamic linking. A program is translation units linked together. It doesn't matter if the linking is done in two steps (first create a .dll/.so, and then the dynamic linker links all dynamic libs + executable together).

So, in my interpretation, it doesn't matter whether a program is dynamically or statically linked, the implementation should behave the same: a class static variable should be unique (no matter whether it's inline or not).

On Linux, this is true.

On Windows, this doesn't work in all circumstances, so in my interpretation, it violates the standard in these circumstances (if you create a separate .dll, which contains the static, non-inline variable, and all other .dll's and the exe refers to this variable, it works).

Election answered 17/7, 2018 at 7:53 Comment(11)
Hi @geza. I think your answer + my answer = complete answer, upvoted.Poorly
So, in other terms (correct me if I'm wrong), it should work as expected but it doesn't on Windows (that is a platform of which I care), so I cannot rely on this solution. Got it right?Dead
@skypjack: I'd say you should try this out on windows. Or even, ask a new question with windows tag about this issue on windows. Hopefully there are windows gurus here, who can elaborate more on this issue. But even, you should try this out. In the past, MSVC was known for bad standard conformance. Nowadays, they try to be standard conformant, but they need to stay compatible with old stuff as well.Election
@skypjack: but, as far as I know, if you put the static variable into a dll (not into the exe), and you link this dll into other dlls (which use the variable), you're fine on Windows too.Election
One more thing to add to the list of absurdities of Windows dlls I guess.Fachini
@PasserBy: I'm no windows expert, that's why I recommended a new windows specific question. As far as I know, on windows, one has to specify all dependencies when linking a dll. On linux, we can leave symbols unresolved when creating a .so, so the OP's issue solves automatically in an easy way. That's an important difference between the two systems. But maybe this is not true anymore, I'm not up-to-date with windows lately (and maybe there is a workaround which works for all old windows, who knows...).Election
Yeah, @skypjack. If you're worried about this [on Windows], try it out! (And post back. I agree that it is a legitimate concernt, those MSVC guys are always pulling these crazy stunts :). But I think I said that already, all this chit-chat is just cluttering up the thread, c'mon guys.Poorly
@Dead it has been already answered for Windows: https://mcmap.net/q/134805/-what-happens-to-global-and-static-variables-in-a-shared-library-when-it-is-dynamically-linked/1207195 (and, at least in this case, it's not about compiler comformance)Coheman
@AdrianoRepetti Thanks for the link!!Dead
@AdrianoRepetti The other question around says - dynamic libraries are not really defined by the C++ standard, this is platform-specific territory, meaning it is much less reliable / portable. I mean, it looks like everything is suggesting that it could work but you aren't guaranteed it will work, even if it does it today. Am I wrong? Because I've also alternative solutions at hand, maybe it's not a good idea to start struggling with shared libraries this way. Does it make sense?Dead
Yes, it depends on the platform and well, it MIGHT be done both on Windows and Linux (I suppose also in most other *nix). Portable? No. Stable? Platform specific but yes (if it's not an hack but you're using OS). Not sure what your final goal is.Coheman
N
8

C++ currently does not have a concept of shared libraries. So the way inline behaves across shared libraries would be implementation- and platform-specific.

The fact that [basic.link]/1 states that "A program consists of one or more translation units linked together." doesn't quite mean that a program linked together with another, already linked module, should behave the same.

A lot of proposals have been submitted over the years to rectify the situation (N1400, N1418, N1496, N1976, N2407, N3347, N4028), none of which took off the ground. It's just hard to implement in a generic way, and C++ generally tries to stay out of implementation details. As GCC put it:

For targets that do not support either COMDAT or weak symbols, most entities with vague linkage are emitted as local symbols to avoid duplicate definition errors from the linker. This does not happen for local statics in inlines, however, as having multiple copies almost certainly breaks things.

MSVC does not expose any symbols by default. Any "external" symbol needs to be explicitly declared with a platform-specific __declspec(dllexport). One can't claim Windows to be incompatible with C++ because of this. None of C++ rules are violated here, because there aren't any.

Necking answered 19/7, 2018 at 7:45 Comment(1)
None of C++ rules are violated here, because there aren't any. Chapeau. +1Dead
P
3

Is there any guarantee regarding the uniqueness of an inline variable when it is used across boundaries or is it simply an implementation detail that I should not rely on?

It's up to you to ensure this (by making sure that all the declarations are in fact the same).

The compiler obviously can't check this and the linker doesn't bother. So if you lie to the linker (by not doing the above), then you will end up in trouble.


OK, since not everyone's getting what I mean by 'lie to the linker', I'll flesh it out a bit.

@oliv kindly supplied this link, which amongst other things says this (commentary mine):

Duplicate copies of these constructs [i.e. variables declare inline in multiple TU's] will be discarded at link time.

Which is fine, that's what we need. Thing is, you don't know which ones (obviously, only one is retained, so by extension, you don't know which one that will be).

So, if they differ, you don't know which one you're going to end up with and so what you end up with is (a particularly insidious form of) UB. That's what I meant by 'lie to the linker'. Because, by declaring your variables differently in different TU's, that's exactly what you did. Whoops!

Poorly answered 17/7, 2018 at 6:38 Comment(14)
It will help future readers (and me actually) if you give more details about what ensure this and lie to the linker means. Maybe some examples? Would you mind elaborating a bit more your answer? Thank you.Dead
Lol. OK, done that, thanks, good point. ... And now I've improved it a bit more, serves me right for rushing just before breakfast.Poorly
Not a problem. Thank you fro being so patient indeed. I've a question now. You say - It's up to you to ensure this (by making sure that all the declarations are in fact the same). It means that it works fine across boundaries if I put the declaration of an inline variable in a .hpp file of a header-only library, then I include it from translations units both of an executable and of a shared library linked by the executable itself? Consider that the library could be created from someone else later in time.Dead
No worries. Patience is a virtue and I don't do it very well myself so a chance to practise is always welcome. And the answer to your question, I believe, is yes, but if you have any doubts then I would knock up a quick test program to be sure.Poorly
Well this answer does not make any progress towards a clear solution for the question. Is the assumption about the uniqueness across library boundries undoubtedly guaranteed by the standard? That is the question. And all i see here is "[...] I believe, is yes". Yeah i think so too. But this is not what OP wants. He wants facts and guarantees. And just testing it for every platform may not be enough, since if its not guaranteed, then a compiler/linker update may change its behaviour later on. This is just a rushed answer with no facts. sorryEnteritis
By declaring your variables differently in different TU's you violate the ODR, which makes the program ill-formed, regardless of any static or dynamic linking.There are many other ways to make the program ill-formed.Gratify
Its obvious that you have to provide the exact same declaration. That's the first precondition and we know that already, because this is stated clearly in the standard. But that is not exactly the question, which is: I have an inline variable v in a header, include this header into a static library s, a dynamic library d, and into the executeable e. Is the linker and the runtime "smart" enough to manage that all (the librarys and the exe) refer to the IDENTICAL variable with the exact same address. Or can it happen, that one of these is working with its own copy and so we dont truly share it.Enteritis
@nm Ah, yes. But what happens when you do (and why)?Poorly
@PaulSanders It doesn't matter what happens, since it's undefined behaviour.Enteritis
@PaulSanders I have curiosity. I want to know whether i can relay on the uniqueness of the inline variable or not. I am not interested to known what could happen if i break the rules. (And just to answer your curiosity: The linker would probably just pick the first definition it sees. But technically the application could do anything, for example formating your harddrive (but i guess that it wont ;-) ))Enteritis
In the gcc doc, "at link time" means during linking phase of different object files (.o) to build either an executable or a shared library. This happens before the code is executed, so there are no UB here. The problem happens when shared library images or loaded into memory at execution time.Allegro
@oliv OK. Please pretend I wrote "you lied to the linker and / or loader". Same difference. Or put it another way (I reckon): "Don't declare inline variables in header files in library code". Because, let's face it, what's the point? Which is intended as a nice way of saying: "guys, guys, let's not get too hung up on this, we're in danger of mistaking the wood for the trees here". Or whatever.Poorly
@PaulSanders That is C++, we are in danger (from Wallon necessity) and so we must understand what is going to happen. There is realy no UB, UB is a standardized terms which qualify an operation that the compiler is free to suppose it will never happen (that helps optimization). Here the only issue is that its numbers will change from compilation to compilation and will change if shared library are dynamically loaded in different orders.Allegro
Sorry @Oliv, I have no idea what you are talking about and would prefer it if you didn't message me again.Poorly

© 2022 - 2024 — McMap. All rights reserved.