Cppcoreguidelines: F5 is horribly wrong. You should ALWAYS declare your function as inline if it does not mean extern.

Created on 1 Jun 2020  路  22Comments  路  Source: isocpp/CppCoreGuidelines

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#f5-if-a-function-is-very-small-and-time-critical-declare-it-inline

inline has nothing to do with optimizing.
"Some optimizers are good at inlining without hints from the programmer but don鈥檛 rely on it. Measure! Over the last 40 years or so, we have been promised compilers that can inline better than humans without hints from humans. We are still waiting. Specifying inline encourages the compiler to do a better job."
It is a common misunderstanding of what inline means. Even this C++ Core Guideline got it horribly wrong.
You should always declare a function as inline, static, or extern. Not writing inline is the number one reason for binary bloat. inline is just like static, it means the function or the variable is an internal linkage. It has nothing to do with "inlining"

Look at this Godbolt. https://godbolt.org/z/PoDJ97
k() function gets compiled as dead function code EVEN no one is calling it. This is at the highest optimization level.

Even this security paper talked about this issue too.
https://www.usenix.org/system/files/conference/usenixsecurity18/sec18-quach.pdf
image

Most helpful comment

I don't know why you had to create a new account to add this issue, but if it's because your other one was blocked maybe you should take the hint to stop being so aggressive. Your approach just makes people ignore you.

All 22 comments

inline is just like static, it means the function or the variable is an internal linkage.

That's nonsense, it doesn't mean internal linkage at all.

It does allow the compiler to not emit a definition if it's not needed (which seems to be your main point), but that's not the same as internal linkage.

It has nothing to do with "inlining"

That's wrong for at least one major compiler, where it is indeed used as a hint that makes it more likely the compiler will inline it. "Specifying inline encourages the compiler to do a better job" is correct.

It does allow the compiler to not emit a definition if it's not needed (which seems to be your main point), but that's not the same as internal linkage

Then this is WHY you should always declare your function as inline if it is not meant to be used outside the translation unit, even it is a recursive function because the binary bloat will affect your performance and be a huge security problem.

I've seen a lot of stupid C++ projects write their stupid non-inline getter/setter outside a class without declaring them as inline.

This is also bitching about binary bloat for the template is stupid because it indicates inline by default.

"Exception Do not put an inline function in what is meant to be a stable interface unless you are certain that it will not change. An inline function is part of the ABI."

The entire ABI stuff is just stupid. The translation unit abuses are the root cause of all binary bloat.
Funny, people think templates bloat binary while virtual functions do not.

std::locale in libstdc++ inheritance bloat the binary for 700kbs. The compiler has no way to eliminate dead virtual function. std::locale is a violation of zero-overhead principle. Unlike exceptions and rtti, there are no -fno-locale toggle. WHY WHY WHY????? I fucking want this feature.

Also, this is why this fmtlib's reducing binary size is so freaking stupid.
https://www.zverovich.net/2020/05/21/reducing-library-size.html

https://github.com/expnkx/fast_io/commit/8cf7497593eb185bba13ad0154a6dfab5a534388

Then this is WHY you should always declare your function as inline if it is not meant to be used outside the translation unit, even it is a recursive function because the binary bloat will affect your performance and be a huge security problem.

No, that's a reason to put it in an unnamed namespace, not make it inline.

It's hard to take your absolutes like "always" and "must" seriously when you make factually incorrect statements and can't remain civil in a github comment. You're not going to convince anybody like this.

I don't know why you had to create a new account to add this issue, but if it's because your other one was blocked maybe you should take the hint to stop being so aggressive. Your approach just makes people ignore you.

Then this is WHY you should always declare your function as inline if it is not meant to be used outside the translation unit, even it is a recursive function because the binary bloat will affect your performance and be a huge security problem.

No, that's a reason to put it in an unnamed namespace, not make it inline.

It's hard to take your absolutes like "always" and "must" seriously when you make factually incorrect statements and can't remain civil in a github comment. You're not going to convince anybody like this.

https://en.cppreference.com/w/cpp/language/inline

If an inline function or variable (since C++17) with external linkage is defined differently in different translation units, the behavior is undefined.

The inline specifier cannot be used with a function or variable (since C++17) declaration at block scope (inside another function)

The inline specifier cannot re-declare a function or variable (since C++17) that was already defined in the translation unit as non-inline.

The implicitly-generated member functions and any member function declared as defaulted on its first declaration are inline just like any other function defined inside a class definition.

If an inline function is declared in different translation units, the accumulated sets of default arguments must be the same at the end of each translation unit.

In C, inline functions do not have to be declared inline in every translation unit (at most one may be non-inline or extern inline), the function definitions do not have to be identical (but the behavior of the program must not depend on which one is called), and the function-local statics are distinct between different definitions of the same function.

See static data members for additional rules about inline static members

Inline variables eliminate the main obstacle to packaging C++ code as header-only libraries.

Clearly there is a relationship between inline and linkage. Or what is the point of making a variable inline in C++17?

Then this is WHY you should always declare your function as inline if it is not meant to be used outside the translation unit, even it is a recursive function because the binary bloat will affect your performance and be a huge security problem.

No, that's a reason to put it in an unnamed namespace, not make it inline.

It's hard to take your absolutes like "always" and "must" seriously when you make factually incorrect statements and can't remain civil in a github comment. You're not going to convince anybody like this.

Unnamed namespace does not solve anything when I try to package my code without multiple definitions. Clearly marking the function as inline is right. Not using static or unnamed namespace.

"An inline function or inline variable (since C++17) has the following properties:

The definition of an inline function or variable (since C++17) must be reachable in the translation unit where it is accessed (not necessarily before the point of access).
An inline function or variable (since C++17) with external linkage (e.g. not declared static) has the following additional properties:
There may be more than one definition of an inline function or variable (since C++17) in the program as long as each definition appears in a different translation unit and (for non-static inline functions and variables (since C++17)) all definitions are identical. For example, an inline function or an inline variable (since C++17) may be defined in a header file that is #include'd in multiple source files.
It must be declared inline in every translation unit.
It has the same address in every translation unit."

Clearly, since C++17, inline has something to do with linkage. It is just false to claim it has nothing to do with linkage.

https://en.cppreference.com/w/cpp/language/inline

Clearly there is a relationship between inline and linkage. Or what is the point of making a variable inline in C++17?

It allows the variable to be defined in multiple translation units, and all the definitions refer to the same entity ("It has the same address in every translation unit"). Just like an inline function. If it gave internal linkage, every translation unit would have a different variable/function. The difference should be obvious, try setting it in one TU and read the value in another TU.

You even quoted the text that says "An inline function or variable (since C++17) with external linkage" so obviously you're wrong to claim it's the same as internal linkage.

Maybe you should take the time to learn how C++ works before making demands about how people use it.

It allows the variable to be defined in multiple translation units, and all the definitions refer to the same entity ("It has the same address in every translation unit"). Just like an inline function. If it gave internal linkage, every translation unit would have a different variable/function. The difference should be obvious, try setting it in one TU and read the value in another TU.

Then how to correctly eliminate the unused functions in that paper? Declaring it as inline right?

Clearly marking the function as inline is absolutely right.

Clearly you can only think in absolutes.

Clearly, since C++17, inline is always the right choice compared to static.

Clearly, since they do different things, they have different uses and sometimes one is the right choice.

Then how to correctly eliminate the unused functions in that paper? Declaring it as inline right?

It depends how/where it's used. There are several ways to tell the compiler that a function is unused and will not be referenced from another translation unit. It's stupid to insist there is one way which is always better than the others, in all situations.

It depends how/where it's used. There are several ways to tell the compiler that a function is unused and will not be referenced from another translation unit. It's stupid to insist there is one way which is always better than the others, in all situations.

"Removal of unused functions require additional non-standard often-unused compiler
(-fdata-sections -ffunction-sections -Os) and linker (-Wl,--gc-sections) optimization flags.
Even so, unused functions in dynamically loaded libraries can not be eliminated during compile time."
I tried this, the binary is still larger than just marking my functions as inline. This is the only standard-compliant way to do the thing.
BTW, I failed to see how you can do this with virtual functions.

image
The binary bloat of std::filebuf is ridiculous because of std::locale and unfortunately, there are no way to eliminate those dead virtual functions. I do not know what solutions do you have. Even using C++20 format will still bloat the binary since it relies on iostream and introduces the link to std::locale.

I think the only solution is to completely replace iostream with something else and extend the inline rule to virtual functions.

This seems off-topic for the core guidelines.

This seems off-topic for the core guidelines.

The guideline is a guideline. It is to tell people what is the best practice. Yes, marking every function as inline if the user does not want it to be extern or static is right. Just like marking every virtual function as "virtual", "override", "final". Here should be every function should be declared exactly one "extern", "inline" or "static". Of course, in the future, you will also declare your function as "noexcept", "throws" or "fails". WHY WHY WHY????? Why declaring a function in C++ is so hard and chaos?
I've said those "several ways to tell the compiler that a function is unused and will not be referenced from another translation unit" do not work in the real world. Most people do not know and won't enable it. Virtual functions for example are technically impossible to remove.

Declaring all your functions inline isn't guaranteed to do what you want it to, and would introduce new bugs. For example, an inline function that has a static variable in it (not such a great idea), would lead to an instance of that variable for every compilation unit. Right?

If what you're looking for is to reduce code size, I'd recommend reading about Link Time Optimization (LTO). Try adding -flto to your flags when linking.

And if you're willing to dive into it, I'd recommend trying to use compile-time code to make it explicit to the compiler when it should generate code or not. Not my area of expertise anymore, but I'm sure the right set of templates or constexpr can give the compiler the hints it needs to optimize your code.

(BTW, This discussion reminds me of the discussion in #1584, where one side makes claims and throws abuse, and @jwakely responds.)

(BTW, This discussion reminds me of the discussion in #1584, where one side makes claims and throws abuse, and @jwakely responds.)

It's the same person.

Declaring all your functions inline isn't guaranteed to do what you want it to, and would introduce new bugs. For example, an inline function that has a static variable in it (not such a great idea), would lead to an instance of that variable for every compilation unit. Right?

If what you're looking for is to reduce code size, I'd recommend reading about Link Time Optimization (LTO). Try adding -flto to your flags when linking.

And if you're willing to dive into it, I'd recommend trying to use compile-time code to make it explicit to the compiler when it should generate code or not. Not my area of expertise anymore, but I'm sure the right set of templates or constexpr can give the compiler the hints it needs to optimize your code.

(BTW, This discussion reminds me of the discussion in #1584, where one side makes claims and throws abuse, and @jwakely responds.)

-flto does not work. You have to do with -fdata-sections -ffunctions-sections -Wl,--gc-sections as the security paper mentioned. Even so, the compiler will still fail to optimize everything away. That was why they created the piecewise loading software for debloating.
BTW, virtual functions will never get debloated, that is why you should restrict any use of virtual if you want small binary size.
In general, you should always mark your function as inline if that function is not meant to be exported in the translation unit.

Binary bloat is not only a performance issue but a security issue too. When your binary size bloats, you create a huge attack surface for an attacker to exploit your program with return-oriented programming.

Of course, shared libraries are also very harmful to security too since shared libraries will always bloat and nothing will get actually shared in real world. Things like ASLR will slow down your performance for even up 20% in many research. Even so, most people won't build their programs with -fPIC or -fPIE which means ASLR does not work at all.

In general, yes, mark all your functions as inline and avoid all dynamic stuff including virtual functions and shared libraries.

Binary bloat is not only a performance issue but a security issue too. When your binary size bloats, you create a huge attack surface for an attacker to exploit your program with return-oriented programming.

If an attacker can set up their ROP sled the game is already over.

Binary bloat is not only a performance issue but a security issue too. When your binary size bloats, you create a huge attack surface for an attacker to exploit your program with return-oriented programming.

If an attacker can set up their ROP sled the game is already over.

False. If an attacker can set up their ROP only if you provide enough gadgets. A lot of security research is about how to reduce the gadgets of ROP. Including return-to-libc, syscall entry etc. If you do not provide enough gadgets, even your program is bugged and got attacked, they won't be very easy to compromise your computer. If you provide a huge amount of attacker surface, ROP is very easy.

You should learn more about attack surface

For something a bit off-topic, what does an extern inline function mean? g++ accepts it and emits no assembly. However, because it has external linkage (i.e. I can link to it from another translation unit), how would the compiler know the contents if it only had an object file for it?

The problem I see here is that most C++ compilers do not process multiple translation units at once (inherited from C, prevalent in build systems, easily parallelized) and, as such, cannot assume that the entire program is being observed. If a compiler wanted to remove a random function, it would have to prove that the function is not used anywhere else, which is a difficult prospect, especially when extern "C" is involved.

This discussion reminds me of a little joke that the most free function declarations for C++ would look like constexpr auto func() noexcept if people knew what those keywords meant, but most people do not write that. In that case, the compiler and language should work on improving defaults. Lambdas fixed some things (e.g.: default operator() is const), but C++ may never improve raw function declarations. The burden is, in the general use case (which these guidelines are promoted for), a compiler issue. Any talk of performance or binary file size is something that is first solved with compiler flags (-O2 and -Os respectively) and then with additional hand tuning.

For something a bit off-topic,

Yes, it's off-topic here.

what does an extern inline function mean? g++ accepts it and emits no assembly. However, because it has external linkage (i.e. I can link to it from another translation unit), how would the compiler know the contents if it only had an object file for it?

The standard requires that an inline function is defined in every translation unit that uses the function. So no definition needs to be emitted in files that don't use it, because it's guaranteed that if another file needs the definition, it can see that definition and compile it for that other file.

You never need to "link to" an inline function, because it's defined in every file that uses it (and then the linker discards duplicate definitions at link time, so only one copy of it remains in the executable or dynamic library).

Was this page helpful?
0 / 5 - 0 ratings