Reputation: 262
Consider:
Makefile
OPT = -O3
CFLAGS = $(OPT) -Wall -Wextra
CC = gcc
default: main.c a.c b.c
$(CC) $(OPT) $^ -o exec
a.h
#ifndef A_H
#define A_H
inline
int f()
{
return 0;
}
#endif
a.c
#include "a.h"
// extern inline int f();
b.c
#include "a.h"
int g()
{
return f();
}
main.c
#include "a.h"
int main () {
return f();
}
This compiles as is. When OPT = -O0
this fails:
"_f", referenced from: _main in ccSN57dj.o ld: symbol(s) not found for architecture x86_64 collect2: error: ld returned 1 exit status
when the line in a.c
is commented out, this compiles again.
I have read:
I understand the reason for this is that the inline definitions emit no object code and at lower optimization levels they are not inlined, so there is no definition to be found at linkage.
Referencing:
A function where at least one declaration mentions inline, but where some declaration doesn't mention inline or does mention extern. There must be a definition in the same translation unit. Stand-alone object code is emitted (just like a normal function) and can be called from other translation units in your program.
This is why commenting out the line in a.c
works.
My question is multi-fold:
extern
keyword states that something is defined in another module, so why is it okay for it to be defined in the same? Are there different interpretations of extern
?external definition
then why does this method compile with no warnings and provide a definition when running objdump -d
?The second one is the more puzzling, since according to C 2018 6.7.4 7 :
An inline definition does not provide an external definition for the function, ...
but the code compiles with no warnings.
Upvotes: 1
Views: 176
Reputation: 679
I've learned that the extern keyword states that something is defined in another module
No. It means that that something has external linkage. Each declaration of an object with external linkage, across the whole program, denotes the same object, but doesnt have to be on a different "module" (translation unit).
When you couple this with inline it basically means that all translation units declare and define the same object, which is perfectly fine as long as all definitions are exactly the same. If any of the definitions differs from the others, you have walked into undefined behavior land.
Why is it okay for there to be multiple definitions, spawned from the different inline definitions in the header files?
Because the standards says it is.
6.7.4 Function specifiers
- [...] If a function is declared with an inline function specifier, then it shall also be defined in the same translation unit. 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.
This paragraph talks specifically about inline functions with external linkage because if they had internal linkage it would obviously be fine as well because each definition would be internal to its own translation unit.
Upvotes: 0
Reputation: 67546
You do not have to create many files.
inline int f()
{
return 0;
}
int main(void)
{
f();
}
https://godbolt.org/z/bKrK1PYeh
will not link. inline functions in GCC do not have external linkage unless they are declared as extern
. As there is no optimization enabled - no inlining is done as well.
You need to declare it as extern
to force gcc to emit f
with external linkage.
extern inline int f()
{
return 0;
}
int main(void)
{
f();
}
https://godbolt.org/z/habdd6ddK
or with optimizations enabled - external version is also provided
https://godbolt.org/z/dT79n4aYo
But it will not link if you include the .h file in more than one compilation unit.
Per GNU documentation:
The extern-inline module supports the use of C99-style extern inline functions so that the code still runs on compilers that do not support this feature correctly.
C code ordinarily should not use inline. Typically it is better to let the compiler figure out whether to inline, as compilers are pretty good about optimization nowadays. In this sense, inline is like register, another keyword that is typically no longer needed.
Functions defined (not merely declared) in headers are an exception, as avoiding inline would commonly cause problems for these functions. Suppose aaa.h defines the function aaa_fun, and aaa.c, bbb.c and ccc.c all include aaa.h. If code is intended to portable to non-C99 compilers, aaa_fun cannot be declared with the C99 inline keyword. This problem cannot be worked around by making aaa_fun an ordinary function, as it would be defined three times with external linkage and the definitions would clash. Although aaa_fun could be a static function, with separate compilation if aaa_fun is not inlined its code will appear in the executable three times.
To avoid this code bloat, aaa.h can do this:
#include any other headers here */ #ifndef _GL_INLINE_HEADER_BEGIN #error "Please include config.h first." #endif _GL_INLINE_HEADER_BEGIN #ifndef AAA_INLINE # define AAA_INLINE _GL_INLINE #endif ... AAA_INLINE int aaa_fun (int i) { return i + 1; } ... _GL_INLINE_HEADER_END ``` and aaa.c can do this:
/* aaa.c */
#include <config.h> #define AAA_INLINE _GL_EXTERN_INLINE #include <aaa.h>
whereas bbb.c and ccc.c can include aaa.h in the usual way. C99 compilers expand AAA_INLINE to C99-style inline usage, where aaa_fun is declared extern inline in aaa.c and plain inline in other modules. Non-C99 compilers that are compatible with GCC use GCC-specific syntax to accomplish the same ends. Other non-C99 compilers use static inline so they suffer from code bloat, but they are not mainline platforms and will die out eventually.
_GL_INLINE is a portable alternative to C99 plain inline.
_GL_EXTERN_INLINE is a portable alternative to C99 extern inline.
Invoke _GL_INLINE_HEADER_BEGIN before all uses of _GL_INLINE in an include file. This suppresses some bogus warnings in GCC versions before 5.1. If an include file includes other files, it is better to invoke this macro after including the other files.
Invoke _GL_INLINE_HEADER_END after all uses of _GL_INLINE in an include file.
Upvotes: -1