I was having an odd problem that I narrowed down to the following test case:
inl.h:
inline const char *fn() { return id; }
a.cc:
#include <stdio.h>
static const char *id = "This is A";
#include "inl.h"
void A()
{
printf("In A we get: %s\n", fn());
}
b.cc:
#include <stdio.h>
static const char *id = "This is B";
#include "inl.h"
void B()
{
printf("In B we get: %s\n", fn());
}
extern void A();
int main()
{
A();
B();
return 0;
}
Now when I compile this with g++ -O1 a.cc b.cc
it seems to work correctly. I get:
In A we get: This is A
In B we get: This is B
but if I compile with g++ -O0 a.cc b.cc
I get:
In A we get: This is A
In B we get: This is A
Note that I'm actually trying to use C11 semantics here, but I'm using g++ as gcc doesn't support C11 yet.
Now as far as I can see, looking at both the C11 spec and the C++ spec (C++11 and older specs -- the semantics of inline and static globals does not seem to have changed), it should do what I want, and the failure when using -O0
is a bug in gcc.
Is this correct, or is there something somewhere in the spec that I'm missing that would make this undefined behavior?
Edit
The common answer seems to claim that fn
needs to be declared as static
for this to work. But according to 6.7.4.6 of the C99 spec (6.7.4.7 in the C11 spec -- not sure about the C++ spec):
If all of the file scope declarations for a function in a translation unit include the inline function specifier without extern, then the definition in that translation unit is an inline definition. An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit.
So since there's no explicit extern
here, these should be two independent inline functions with no interaction with each other. No explicit static
is required.
Using an explicit static fixes the problem for C, but doesn't work for C++ inline member functions, as the static
keyword has a completely different meaning in that case.
Because fn()
is not declared static
, as @RobKennedy points out, you're venturing into UB land. In this specific case, -O0
probably disables inlining, which means that one of the emitted non-inlined versions of the function will be kept and the other discarded, and both calls will be to the one non-inlined version. Whether the A version is always kept may depend on the order in which you specify your files on the command line, or any number of other things. -O1
probably included inlining, in which case, even if there is a non-inlined copy of the function emitted, the two calls may still be inlined, which gives the (erroneously) expected results.
You've violated the one-definition rule. The non-static function fn
is defined differently in your two translation units. One binds with the id
variable defined in a.cc, where as the other binds with the id
variable in b.cc. The definitions are textually identical, but that's not enough to satisfy the one-definition rule, even with the exception set out for functions declared inline
, so you get undefined behavior.
You're using a C++ compiler, not a C compiler, so whatever C11 says is irrelevant with regard to the behavior your C++ program exhibits. In C++11, the standard (at §3.2/5) seems to state the rule about how fn
is allowed to reference id
(emphasis and ellipses mine):
There can be more than one definition of a … inline function with external linkage (7.1.2) … in a program provided that each definition appears in a different translation unit, and provided the definitions satisfy the following requirements. Given such an entity named
D
defined in more than one translation unit, then
- each definition of
D
shall consist of the same sequence of tokens; and- in each definition of
D
, corresponding names, looked up according to 3.4, shall refer to an entity defined within the definition ofD
, or shall refer to the same entity, after overload resolution (13.3) and after matching of partial template specialization (14.8.3), except that a name can refer to aconst
object with internal or no linkage if the object has the same literal type in all definitions ofD
, and the object is initialized with a constant expression (5.19), and the value (but not the address) of the object is used, and the object has the same value in all definitions ofD
; and- …
Your definitions of fn
consist of the same sequence of tokens, but they refer to id
, which is not defined within D
, is not the same entity in both translation units, and does not have the same value in all definitions. I see no provision in the C++ standard for an inline function acquiring internal linkage implicitly. C++11 §7.1.1/7 says this:
A name declared in a namespace scope without a storage-class-specifier has external linkage unless it has internal linkage because of a previous declaration and provided it is not declared
const
.
If you got your expected behavior at certain optimization levels, or from certain versions of certain compilers, then you were just getting the particularly nefarious version of undefined behavior, where things appear to work despite being wrong.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With