C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Drop same sequence of tokens for inline

From: David Brown <david_at_[hidden]>
Date: Fri, 5 May 2023 18:47:59 +0200
On 05/05/2023 17:20, Alejandro Colomar via Std-Proposals wrote:
> Hi David,
>
> On 5/5/23 12:51, David Brown via Std-Proposals wrote:
>> On 05/05/2023 02:07, Alejandro Colomar via Std-Proposals wrote:
>>> Hi,
>>>
>>> On 5/4/23 21:24, Tom Honermann via Std-Proposals wrote:
>>>> On 5/4/23 1:11 PM, sasho648 via Std-Proposals wrote:
>>>>> So why does inline functions need to have the same sequence of tokens
>>>>> in different TU - imagine in a TU there is a preprocessor define that
>>>>> changes the function definition - it would make sense this not to be UB.
>>>>>
>>>>> In C inline functions have internal linkage - it would make the same
>>>>> sense for C++.
>>>
>>> What? C inline functions have the same linkage as any other function:
>>> external by default; internal if you use 'static'.
>>>
>>> Here's how you should use inline in C:
>>>
>>> // foo.h:
>>> inline int
>>> foo(void)
>>> {
>>> return 42;
>>> }
>>>
>>> // foo.c:
>>> extern inline int foo(void);
>>>
>>> Here's a great description of how C inline works:
>>> <https://www.greenend.org.uk/rjk/tech/inline.html>
>>>
>>
>> That link is rather dated - as is your advice on how you "should" use
>> inline in C.
>>
>> The prime use of "inline" in modern C on modern compilers is as
>> information to the human programmers reading the code. There are very
>> few use-cases where "inline" makes sense without "static",
>
>
>
>> and then the
>> "inline" modifier is pretty much redundant except to tell readers that
>> this is something you view as a small helper function
>
> Yes, inline is redundant in static inline. I never write static inline
> functions, since I would only write them in .c files, but then static is
> enough.

I, on the other hand, never use "inline" on a function that is /not/
static. I always know if the use of the function would require using a
non-inline external definition. (Note that it's fine by me if the
compiler chooses to generate fully or partially non-inlined code based
on optimisation and code analysis - I'm talking about the logical
handling of the code, not the details of the code generation.) The
compiler could still use a non-inlined version of my static definition -
I usually have "-Winline" in effect with gcc, which will warn me if it
does. And in important cases, I use gcc's "always_inline" attribute,
though of course that is not fully portable.

So all my inline functions are "static inline". To me, "static inline"
is more an indication of the programmer's intent and the meaning and use
of the code - I think it is safe to say that any time I have used
"inline", the specifier could be removed without actually changing the
behaviour of the code.

These things are often a matter of style and habit. I also have a habit
of labelling external function declarations in headers with "extern" -
the specifier is redundant as far as the compiler is concerned, but I
believe it is clearer to the reader.


>
>> - the kind of
>> function previous generations might have written as function-like macros.
>
> For this, extern inline is best. Let me explain.
>

You are welcome to try to convince me :-)

(Again - I can only say what is best for /me/, and not what is best for
others.)

>>
>> Consider the difference between a static inline function "foo", and a
>> non-static inline function "bar" with the same functionality.
>>
>> With "foo", you always know exactly what definition is used for the
>> function. With "bar", there is a guessing game - the compiler might use
>> the local inline definition, or it might use an external definition.
>
> You probably refer to C11::6.7.4p7:
> It is unspecified whether a call to the function uses the
> inline definition or the external definition.
>

Yes.

> While the standard allows programmers to write such guessing games, it's
> the programmer's fault if they write more than one definition for an
> inline function. The model I showed where there's exactly one
> source-code definition in the header file, and one extern prototype
> (used for the external-linkage definition) in a single TU doesn't have
> guessing issues.
>
> So, this one is not really an issue.

Yes, it is an issue.

The language does not force the programmer to make their external
definition in the way you describe. In some cases, it would /not/ be
the best way to do it. The inline version may be written in a way that
is optimal only when it really is inlined, where the compiler can take
advantage of additional information from the context of the call, while
the non-inlined version may be written to be more general. In C, it is
perfectly acceptable for different translation units to have different
inline definitions of the same function, but only one non-inlined
definition in the program.

You might choose to restrict your usage in your own code (and we all
restrict our C programming to whatever we consider to be a "sane" subset
of allowed code), but you need to understand what the language allows
and what others may write.


>
>> In
>> neither case do you have any guarantees that the code is actually
>> generated inline in the caller
>
> That's true, and that shouldn't be a concern. As far as the abstract
> machine is concerned, inline is irrelevant. If one cares about
> performance (and knows more than the compiler, which is rare) and wants
> to force the compiler to inline a function, it should use a compiler-
> specific attribute, such as [[gnu::always_inline]]. inline is not
> the right tool for that. Not even in C++.
>
> So, this shouldn't be an issue either.

Yes, the logical behaviour is always more important than efficiency.
(Though for some uses, efficiency is also a requirement for correctness.)

>
>> - it could be a stand-alone function in
>> the object code.
>
>
>
>> But with "foo", you know the definition for that
>> function, while with "bar" it could be either the inline definition or
>> the external definition.
>
> Again, both should be the same in my model of inline, since the TU that
> defines the external definition uses the same source code from the
> header than the rest of TUs will see.
>
> Not an issue.

Except when it /is/ an issue - because the use of a single definition is
/not/ forced in C. But as long as you are consistent with your inline
definitions, it should not be an issue.

>
>>
>> With "foo", you can have local modifiable static data.
>
> Local to what? In a header file?

Local to the translation unit - just like in any other static function.

>
>> You can refer to
>> other static data and functions in the file. With "bar", you cannot.
>
> I would be very worried about a program whose design depends on static
> storage variables in a header file. In C++, that may be useful. In C,
> I've never seen such a need. However, if you really need it, you can
> use 'static inline' for those very rare cases.
>

Static data within a static inline function in a header could be useful
in some cases - perhaps for a small memorization cache for a function -
but I agree it would be rare. But static inline functions are not just
for headers - they could be within a C file, and then access to static
data (static to the file or to the function) is important. After all,
all objects and functions within the C file should be static unless they
have to be exported - there is no reason to have a non-static inline
function in a C file.

(Static /const/ data is, of course, fine in a header - either within an
inline function definition, or at header file scope. It is even allowed
within a non-static inline function.)

>
>> (gcc and perhaps other compilers are far too lax on these rules - but
>> let's stick to standard C and defined behaviours.)
>>
>> With "foo", you only need the one definition - and you can put it in a
>> header or a C file, according to where you want to use it. With "bar",
>> you /might/ need an external definition as well - or might not,
>> depending on how it happens to be used.
>
> No. You should use C99 inline in this way:
>
> - A single definition in a header file, which will be used for _all_
> inline definitions _and_ the one external definition.
>
> - A single extern inline prototype in a single TU, which will tell
> the compiler to emit the external definition.
>
> So, with "bar", you also only write the function body once. You only
> write an extra prototype.
>

The second version /is/ an external definition, which must be given in
precisely one C file in the program. You don't necessarily need to
write a body for it (indeed, if an inline version is visible then you
are not allowed to write a second body). But you can, if you want,
write a different function body. However, you still need that external
definition, easy though it may be.

>>
>> With "foo", if you actually need a non-inlined version of the function
>> from multiple translation units,
>> and you don't have a smart enough
>> linker that can combine these, you'll waste a bit of space.
>
> Exactly, and there's another reason to avoid it:
>
> Pointers to the same function compare different in different TUs, as
> they are different definitions; but that's not a normal thing I do, so
> I care less about it.
>

They are different functions - of course they compare differently. If
comparing pointers to the function is relevant, then you would be
unlikely to be using inline functions.

Of course if you want the function to be inlined in most cases, and want
it to be used occasionally as a non-inlined function, and want pointers
to the function to compare equal - then use non-static inline functions.
  I am listing the differences between "inline" and "static inline", and
showing why "static inline" is, IMHO, normally the better choice. That
does not mean it is /always/ the right choice.

>> If that
>> space is remotely close to significant, then you should not have been
>> making the function "inline" in the first place.
>
>
>> In real-world usage,
>> the risk of extra space is irrelevant. (And I say that as someone who
>> works on small microcontrollers for a living.)
>
> It may be small, but since there are no advantages of static inline vs
> inline with reasonable uses of inline, I prefer the smaller space, and
> as a side effect, pointers compare equal.
>

The smaller space is irrelevant. Trust me, as someone who has coded for
devices with 2 KB memory, if you worry about the space used by
non-inline copies of static inline functions, you are doing things wrong
in many ways.

I've listed the differences between the two styles. If you prefer the
non-static style, fine. I see advantages in "static inline", and find
it clearer and more consistent. For file-level data and non-inline
functions, it is always better to have "static" when possible - this
gives both the reader and the compiler far more information about how
the data and function may be used. The difference is not nearly as big
for inline functions, but I prefer the consistency.


>
> The issues you've been describing of inline are really issues with the
> old GNU inline, which is crap. C99 doesn't have those issues, unless
> the programmer really writes stupid code (which is allowed). BTW, GCC
> defaults to C99 inline unless you use -std=gnu89; but if you use
> GNU C89, you have many more problems to worry about, of course.
>

No, the issues are not about GNU inline (I quoted from the C standards,
not the GCC manual). It is a long time since I have had to use pre-C99
standards, but when I did, it was certainly nice that "static inline"
works consistently in both GNU inline and C99 inline.

(I always give the standard explicitly, rather than using compiler
defaults.)


I guess we just have different opinions here, and that's fine. I'm sure
there are many differences in our styles and habits. Some differences
will have clear justifications based on the kinds of coding we do, and
the different balances of the pros and cons of different choices.
Others will be habits, or preferences, or relatively minor issues that
could work in many ways.



mvh.,

David

Received on 2023-05-05 16:48:09