Date: Fri, 5 May 2023 16:04:27 +0200
Hi Thiago,
On 5/5/23 03:22, Thiago Macieira via Std-Proposals wrote:
> On Thursday, 4 May 2023 17:54:12 PDT Alejandro Colomar wrote:
>> I still don't understand how use of static within an inline function
>> is not UB. Maybe in C++ it's useful. In C, I never found a need for
>> 'static' within an inline function.
>
> Indeed, but I'd say that it's a consequence of how statics inside inline were
> defined in C. That removed their usefulness, therefore they don't get used. The
> warning also probably exists because people tried to use it and have run into
> issues.
Actually, I'm very confused about static within inline in C, since it seems to
really be UB in one paragraph, and defined behavior a few paragraphs below:
<https://port70.net/~nsz/c/c11/n1570.html#6.7.4p3>
An inline definition of a function with external linkage shall
not contain a definition of a modifiable object with static or
thread storage duration, and shall not contain a reference to
an identifier with internal linkage.
>From the above, it seems UB. But then paragraph 7 has a note:
<https://port70.net/~nsz/c/c11/n1570.html#6.7.4p7>
[...] It is unspecified whether a call to the function uses
the inline definition or the external definition. 140)
OK so far. Here's the note:
<https://port70.net/~nsz/c/c11/n1570.html#note140>
Since an inline definition is distinct from the corresponding
external definition and from any other corresponding inline
definitions in other translation units, all corresponding
objects with static storage duration are also distinct in each
of the definitions.
What? Now the behavior seems to be defined to be distinct in all definitions
instead of the "shall not" happen from 6.7.4p3. Still unspecified which one
gets called.
>
> Static variables inside inline functions aren't that common in C++, except for
> a few very common patterns, such as that of singletons:
>
> class MySingleton
> {
> MySingleton();
> ~MySingleton();
> public:
> static MySingleton *instance()
> {
> static MySingleton self;
> return &self;
> }
> };
>
> Replacing this with a namespace- or class-scope static is not the same thing
> because it changes the lifetime of the object and it may be important that
> this constructor not be run at load time. At a minimum, this is lazy
> initialisation and conforms to "Don't Pay for What You Don't Need" ethos. It
> is possible to write code that keep the ethos and uses a non-vague variable,
> but it requires writing more code; a great deal more if you want to also have
> the thread- and exception-safety guarantee the code above gives you
> implicitly.
Sounds reasonable for C++.
>
>> Is there any way to get UB without 'static' in compatible definitions such
>> as these?
>
> Yes, it's possible. The compiler is allowed to break your function into blocks
> and inline a portion of it and not another. GCC does this quite a lot when it
> decides some expensive code is unlikely. Take this example:
>
But if the compiler decides to emit an external-linkage definition, it must
emit it in its entirety, in case another TU wants to call it.
Internal-linkage partial copies shouldn't be problematic. In the end, it's
more or less what C does, with the exception that C++ allows static.
It was curious to me why C didn't require so much from inline (it's allowed
to provide a different definition in each TU; it's just unspecified which one
will be called), and C++ did require an exact copy everywhere. The only
important difference is that C++ allows static, which may be the reason why.
> void other();
> inline void f1(int i)
> {
> if (i)
> other();
> else
> throw i;
> }
>
> Modern GCC considers that throwing code is unlikely, just as if you had a
> [[likely]] attribute on that first if or __builtin_expected, so it moves the
> else branch to a separate block. You can see it in action in
> <https://gcc.godbolt.org/z/ns6Ph51Go>; note how there's a symbol with "[clone
> .cold]" suffix (if you turn on directives, you'll see that it's even in an
> entirely different section of the executable).
>
> Note also how this is not a new function obeying the regular calling
> convention, but really just a portion of the original and it expects the value
> to be thrown to be in the %eax register. So two different TUs did something
> different and innocent before that if, the register allocation might be
> different and result in garbage. UB.
>
> However, I do note in this case that the [clone .cold] applies to f(int),
> which is not the inline function but the non-inline one into which got f()
> inlined. So this particular version of the compiler wouldn't suffer from that
> problem.
Cheers,
Alex
On 5/5/23 03:22, Thiago Macieira via Std-Proposals wrote:
> On Thursday, 4 May 2023 17:54:12 PDT Alejandro Colomar wrote:
>> I still don't understand how use of static within an inline function
>> is not UB. Maybe in C++ it's useful. In C, I never found a need for
>> 'static' within an inline function.
>
> Indeed, but I'd say that it's a consequence of how statics inside inline were
> defined in C. That removed their usefulness, therefore they don't get used. The
> warning also probably exists because people tried to use it and have run into
> issues.
Actually, I'm very confused about static within inline in C, since it seems to
really be UB in one paragraph, and defined behavior a few paragraphs below:
<https://port70.net/~nsz/c/c11/n1570.html#6.7.4p3>
An inline definition of a function with external linkage shall
not contain a definition of a modifiable object with static or
thread storage duration, and shall not contain a reference to
an identifier with internal linkage.
>From the above, it seems UB. But then paragraph 7 has a note:
<https://port70.net/~nsz/c/c11/n1570.html#6.7.4p7>
[...] It is unspecified whether a call to the function uses
the inline definition or the external definition. 140)
OK so far. Here's the note:
<https://port70.net/~nsz/c/c11/n1570.html#note140>
Since an inline definition is distinct from the corresponding
external definition and from any other corresponding inline
definitions in other translation units, all corresponding
objects with static storage duration are also distinct in each
of the definitions.
What? Now the behavior seems to be defined to be distinct in all definitions
instead of the "shall not" happen from 6.7.4p3. Still unspecified which one
gets called.
>
> Static variables inside inline functions aren't that common in C++, except for
> a few very common patterns, such as that of singletons:
>
> class MySingleton
> {
> MySingleton();
> ~MySingleton();
> public:
> static MySingleton *instance()
> {
> static MySingleton self;
> return &self;
> }
> };
>
> Replacing this with a namespace- or class-scope static is not the same thing
> because it changes the lifetime of the object and it may be important that
> this constructor not be run at load time. At a minimum, this is lazy
> initialisation and conforms to "Don't Pay for What You Don't Need" ethos. It
> is possible to write code that keep the ethos and uses a non-vague variable,
> but it requires writing more code; a great deal more if you want to also have
> the thread- and exception-safety guarantee the code above gives you
> implicitly.
Sounds reasonable for C++.
>
>> Is there any way to get UB without 'static' in compatible definitions such
>> as these?
>
> Yes, it's possible. The compiler is allowed to break your function into blocks
> and inline a portion of it and not another. GCC does this quite a lot when it
> decides some expensive code is unlikely. Take this example:
>
But if the compiler decides to emit an external-linkage definition, it must
emit it in its entirety, in case another TU wants to call it.
Internal-linkage partial copies shouldn't be problematic. In the end, it's
more or less what C does, with the exception that C++ allows static.
It was curious to me why C didn't require so much from inline (it's allowed
to provide a different definition in each TU; it's just unspecified which one
will be called), and C++ did require an exact copy everywhere. The only
important difference is that C++ allows static, which may be the reason why.
> void other();
> inline void f1(int i)
> {
> if (i)
> other();
> else
> throw i;
> }
>
> Modern GCC considers that throwing code is unlikely, just as if you had a
> [[likely]] attribute on that first if or __builtin_expected, so it moves the
> else branch to a separate block. You can see it in action in
> <https://gcc.godbolt.org/z/ns6Ph51Go>; note how there's a symbol with "[clone
> .cold]" suffix (if you turn on directives, you'll see that it's even in an
> entirely different section of the executable).
>
> Note also how this is not a new function obeying the regular calling
> convention, but really just a portion of the original and it expects the value
> to be thrown to be in the %eax register. So two different TUs did something
> different and innocent before that if, the register allocation might be
> different and result in garbage. UB.
>
> However, I do note in this case that the [clone .cold] applies to f(int),
> which is not the inline function but the non-inline one into which got f()
> inlined. So this particular version of the compiler wouldn't suffer from that
> problem.
Cheers,
Alex
Received on 2023-05-05 14:04:47