5

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?

Ebrahim Byagowi
  • 10,338
  • 4
  • 70
  • 81
  • 4
    _" `inline` is a suggestion to C++ compiler for substituting a function whenever it sees better, ..."_ - this is no longer true: `inline` now only means that more than 1 definition is allowed. If you want to suppress the external visibility (linkage) of inline definitions you need to declare them static. Have a read of: http://en.cppreference.com/w/cpp/language/inline – Richard Critten Feb 05 '18 at 11:11
  • Thank you all for the answers, I've upvoted all the answers and will try to mark the most voted one as the accepted some time later :) – Ebrahim Byagowi Feb 05 '18 at 12:03
  • 1
    @EbrahimByagowi See [this](https://stackoverflow.com/questions/45971091/gcc-vs-clang-inlining-a-function-with-fpic) question. It shows effect of ELF symbols interpositions when building PIC. One of key features of `inline` is that it is a standard way to avoid overhead of symbols interpositioning in C++ code. –  Feb 05 '18 at 12:12

3 Answers3

11

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.

  • `C++ requires all functions (including inline functions) to have the same address in all translation units` That is exactly what inline means doesn't have to happen. – UKMonkey Feb 05 '18 at 11:50
  • 4
    No it is not. Inline function can be inlined (in which case that instance has no address), but taking an address of inline function should always give you the same result. –  Feb 05 '18 at 11:51
  • 1
    As per the other answers. Inline is not a hint about inlining code; it literally means it is defined in multiple translation units. The linker will decide to remove the duplicates, but the address in the translation units prior to linking will differ. – UKMonkey Feb 05 '18 at 11:52
  • @UKMonkey It is very much related to inlining when we are talking about linking multiple SO on Linux when using ELF. Public non-inline functions _cannot be inlined_ at all because it violates ODR in the whole program. Inline allows multiple definitions to exist if they are equivalent. If they are not - behavior is undefined. This undefined behavior is what allows compiler to inline the function call on ELF. See `-fno-semantic-interposition` flag in GCC –  Feb 05 '18 at 11:55
  • `Public non-inline functions cannot be inlined at all` sure they can; as you say they become not a function, keeping ODR. What you're talking about is a COMPILER feature where they've opted to obey the hint rather than perform their own optimization; And since the standard says optimization is optional then that's not a problem. In the same way, compilers are not obliged to inline any functions at all, even if marked as inline. It is the fact that the standard has made it all optional that has changed the meaning of inline. – UKMonkey Feb 05 '18 at 12:30
  • 3
    @UKMonkey You are never building "pure C++ program", you are building C++ program for POSIX system with ELF executable format. ELF requires public functions to be called through PLT. Some compilers (Clang) choose ignore this requirement, others (GCC) choose to follow it. You have to understand those things if you want to write C++ programs. C++ standard is not everything and tbh is has the least power in determining how programs should be built. Standards like POSIX can extend and override C++ standard if necessary. –  Feb 05 '18 at 12:34
2

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.

Useless
  • 64,155
  • 6
  • 88
  • 132
  • `inline` is still very much related to inlining on ELF targets where inlining is _forbidden_ without `inline` keyword. –  Feb 05 '18 at 12:04
  • Modern compilers do actually take `inline` into account as an inlining hint, as well as its primary meaning – M.M Feb 05 '18 at 12:17
  • @Ivan - the question linked in your other comment shows clang inlining a call to a function declared without the `inline` keyword, right? – Useless Feb 05 '18 at 12:52
  • @Useless Yes, as I mentioned in my answer Clang chooses to ignore ABI here. GCC still follows it. After all, standards are just paper. Everyone is free to ignore them. IMO clang does the right thing. ELF is just broken here. –  Feb 05 '18 at 12:53
2

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.

sbabbi
  • 11,070
  • 2
  • 29
  • 57