Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why `-fvisibility-inlines-hidden` is not the default?

Tags:

c++

c

gcc

linker

ld

I am asking to see whether my understandings are correct.

inline is a suggestion to C++ compiler for substituting a function whenever it sees better, therefore calling a procedure flagged as inlined from outside of a library shouldn't be reliable and they logically should be hidden by default, preventing others to call them as an update to a compiler or codebase can be change the decision (thus removal of the inlined function and ABI breakage?).

However it seems that is not the default setting and -fvisibility-inlines-hidden should be set to make that happen. I am asking here why that is the case? Does not setting that has any real use case and it is there just because legacy reasons?

like image 549
Ebrahim Byagowi Avatar asked Feb 05 '18 11:02

Ebrahim Byagowi


3 Answers

they logically should be hidden by default

C++ requires all functions (including inline functions) to have the same address in all translation units. Local statics should also be shared among all translation units. Making inline functions hidden violates those requirements if program is built as multiple shared objects (.so files).

Inline functions should have public visibility so that dynamic linker could choose one definition from all existing ones at runtime.

GCC wiki mentions this:

-fvisibility-inlines-hidden can be used with no source alterations, unless you need to override it for inlines where address identity is important either for the function itself or any function local static data.


Consider following example. Executable source:

// main.cpp
#include <cstdio>

struct A { };

inline A &foo()
{
    static A a;
    return a;
}

int main()
{
    void *p = &foo();
    std::printf("main() - %p\n", p);
}

Shared object source:

#include <cstdio>

struct A { };

inline A &foo()
{
    static A a;
    return a;
}

static __attribute__((constructor)) void init()
{
    void *p = &foo();
    std::printf("main() - %p\n", p);
}

If you build both and link executable against this shared object, you can see that foo always returns the same address in both translation units.

Now if you add __attribute__((visibility("hidden"))) to those inline functions, then you will see that address is different in different translation units.

This is not what some C++ program might expect.


Most people today think that inline has nothing to do with actual function inlining. This is not exactly true. ELF targets try to make dynamic linking transparent e.g. program should behave identically if it is build as a single executable or as multiple shared objects.

To make it possible ELF requires all functions with public visibility to be called though GOT or through PLT as if it is "imported" function. This is required so that every function could be overriden by another library (or executable itself). This also forbids inlining for all public non-inline functions (see section 3.5.5 here which shows that public function call in PIC should be made through PLT).

Code inlining for public inline functions is possible exactly because inline allows program behavior to be undefined when multiple definitions of inline function are not equivalent.

Interesting note: Clang violates this ELF requirement and is able to inline public functions on ELF targets anyway. GCC can do the same with -fno-semantic-interposition flag.

like image 90
StaceyGirl Avatar answered Sep 28 '22 15:09

StaceyGirl


inline is a suggestion to C++ compiler for substituting a function whenever it sees better

No. This was originally the case, in perhaps the late 90s, but has not been true for a long time.

See this answer for a good explanation.

therefore calling a procedure flagged as inlined from outside of a library shouldn't be reliable

  1. your initial assumption is already wrong, so the therefore is proceeding from a false premise
  2. even if the compiler does inline a call (which it may do with or without the inline keyword), this is done at a specific call site. Inlining isn't something that happens to a function, which must always be emitted as usual, but to a function call.

    It's entirely possible for the compiler to inline some calls to a function and not others, depending on its opinion of what will produce the best code at the call site.

Now, it's not clear what problem you believe inline causes in libraries, so it's hard to address that directly.

like image 36
Useless Avatar answered Sep 28 '22 15:09

Useless


inline is a suggestion to C++ compiler for substituting a function whenever it sees better, therefore calling a procedure flagged as inlined from outside of a library shouldn't be reliable and they logically should be hidden by default

The compiler can still decide to inline some calls and leave some of them non-inlined. In this case you would get several copy of the inlined function in all the libraries you are linking together.

Moreover the standard somewhat requires that &foo to be the same address everywhere in your program, although the standard does not say anything about DSO/DLLs, so the compilers have some freedom in those regards (in fact, MSVC follows the opposite approach of leaving everything hidden by default).


However it seems that is not the default setting and -fvisibility-inlines-hidden should be set to make that happen.

Despite the name, -fvisibility-inlines-hidden only affects inlined class member functions. For everything else, -fvisibility=hidden should be sufficient.


Does not setting that has any real use case and it is there just because legacy reasons?

Yes. IIRC that flag was implemented in GCC 3.2 (or close enough); making it default would break a lot of legacy code.

like image 37
sbabbi Avatar answered Sep 28 '22 16:09

sbabbi