Date: Sat, 19 Oct 2024 21:59:31 +0200
>> Or dlopen, loadlibrary,... Which are just functions; outside of the standard, like the rest of dynamic linking
>
> Those are also non-standard.
Exactly my point, dynamic linking is non-standard!
>> No need for attributes, assembly, or other compiler extensions, or strange constructs.
>> Global variables are problematic in shared libraries, that's it.
>
> Problematic how?
Time to take out my favorite example:
const std::string answer_of_life = "42";
This line of code, a global constant std::string, can lead to undefined
behavior.
Depending on the environment, you will have one copy, multiple copies,
one copy with the constructor/destructor executed multiple times, and
other things.
There is no function outside of those defined by the standard, not
attributes, nothing.
Normal and boring c++ code.
It's what the runtime is doing behind our back that is problematic, it's
not that if I do no call dlopen explicitly then the problem disappears ;)
As an example, a class that logs in the destructor it's address and a
string:
https://godbolt.org/z/sb8n8fn1a
the output:
> 0x7fd71efe616a goodbye
> 0x7fd71efe6169 goodbye
> 0x7fd71efe016a goodbye
> 0x7fd71efe0169 goodbye
there are thus four independent instances
Same class, slightly different context:
https://godbolt.org/z/Thv8xhc6Y
output:
> 0x7a9bf9cb6169 goodbye
> 0x7a9bf9cb6169 goodbye
one instance, destructor is executed more than once (UB, most probably
you will consume double amount of memory, and eventually notice the
crash at shutdown)
Another example that shows the same behavior
https://godbolt.org/z/ecPPq8vns
Note that in none of those examples, I've used anything outside of the
standard or platform specific.
There are ways to have well-defined behavior (no way to ensure, because
it does not depend on the c++ language, but on your toolchain/environment).
The most secure way is to use constexpr, because no code is executed.
If that would be UB, then you would also have the same issue for
function pointers, which are effectively global symbols that do not
execute any code during load/unload.
Changing visibility settings and hiding everything avoids the UB, but
then you get multiple copies fo the same object.
It could be problematic if some objects handles some types of resources.
inline variables since c++17 help too.
Everything that changes the visibility and linkage (implicitly through
different c++ tools, or explicitly through compiler specific settings),
of the variable will change the observed behavior.
But everything is unspecified (in c++ at least): how many copies you
get, how many times the constructor and destructor are executed on every
instance, and so on.
C++ has no concept of visibility because it has no concept of libraries.
Some results on this particular issue grouped together:
* https://fekir.info/post/global-variables-in-cpp-libraries/
* https://www.youtube.com/watch?v=xOJGc72gf8M
Another issue, that fortunately I've never encountered:
Microsoft documents some things that should not be done in DllMain or
before.
Thus some operations are fine in an application, but not in a dynamic
library:
https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
I'm not sure if similar restrictions exists for shared libraries on
other OS, but it would not surprise me.
Federico
>
> Those are also non-standard.
Exactly my point, dynamic linking is non-standard!
>> No need for attributes, assembly, or other compiler extensions, or strange constructs.
>> Global variables are problematic in shared libraries, that's it.
>
> Problematic how?
Time to take out my favorite example:
const std::string answer_of_life = "42";
This line of code, a global constant std::string, can lead to undefined
behavior.
Depending on the environment, you will have one copy, multiple copies,
one copy with the constructor/destructor executed multiple times, and
other things.
There is no function outside of those defined by the standard, not
attributes, nothing.
Normal and boring c++ code.
It's what the runtime is doing behind our back that is problematic, it's
not that if I do no call dlopen explicitly then the problem disappears ;)
As an example, a class that logs in the destructor it's address and a
string:
https://godbolt.org/z/sb8n8fn1a
the output:
> 0x7fd71efe616a goodbye
> 0x7fd71efe6169 goodbye
> 0x7fd71efe016a goodbye
> 0x7fd71efe0169 goodbye
there are thus four independent instances
Same class, slightly different context:
https://godbolt.org/z/Thv8xhc6Y
output:
> 0x7a9bf9cb6169 goodbye
> 0x7a9bf9cb6169 goodbye
one instance, destructor is executed more than once (UB, most probably
you will consume double amount of memory, and eventually notice the
crash at shutdown)
Another example that shows the same behavior
https://godbolt.org/z/ecPPq8vns
Note that in none of those examples, I've used anything outside of the
standard or platform specific.
There are ways to have well-defined behavior (no way to ensure, because
it does not depend on the c++ language, but on your toolchain/environment).
The most secure way is to use constexpr, because no code is executed.
If that would be UB, then you would also have the same issue for
function pointers, which are effectively global symbols that do not
execute any code during load/unload.
Changing visibility settings and hiding everything avoids the UB, but
then you get multiple copies fo the same object.
It could be problematic if some objects handles some types of resources.
inline variables since c++17 help too.
Everything that changes the visibility and linkage (implicitly through
different c++ tools, or explicitly through compiler specific settings),
of the variable will change the observed behavior.
But everything is unspecified (in c++ at least): how many copies you
get, how many times the constructor and destructor are executed on every
instance, and so on.
C++ has no concept of visibility because it has no concept of libraries.
Some results on this particular issue grouped together:
* https://fekir.info/post/global-variables-in-cpp-libraries/
* https://www.youtube.com/watch?v=xOJGc72gf8M
Another issue, that fortunately I've never encountered:
Microsoft documents some things that should not be done in DllMain or
before.
Thus some operations are fine in an application, but not in a dynamic
library:
https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices
I'm not sure if similar restrictions exists for shared libraries on
other OS, but it would not surprise me.
Federico
Received on 2024-10-19 19:59:43