1

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:

  • I've learned that the 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?
  • Why is it okay for there to be multiple definitions, spawned from the different inline definitions in the header files? If none of these qualify as an external definition then why does this method compile with no warnings and provide a definition when running objdump -d?
  • How would it be chosen between these multiple definitions if they were not the same, given this ambiguity?

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.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
user18348324
  • 244
  • 10
  • 1
    @KamilCuk http://www.greenend.org.uk/rjk/tech/inline.html – user18348324 Mar 01 '23 at 23:41
  • you include the file in all compilation units. – 0___________ Mar 02 '23 at 00:15
  • 1
    (1) The use of `extern` with `inline` is somewhat different from any other use of `extern`. (2) Because the standard says it is OK. (3) You invoke undefined behaviour if the function definition is not consistent. —— My personal recommendation would be "always use `static inline` with the definition of the body of the function in the header" (and use the header wherever you need the function. This avoids pretty much all the nastinesses. (If there is a problem, you are probably trying to inline functions that would be better not inlined.) – Jonathan Leffler Mar 02 '23 at 02:59

2 Answers2

0

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

  1. [...] 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.

markhc
  • 659
  • 6
  • 15
  • So for my question: in `a.c`, the definition is present as `inline int f () {return 0;}` and I have `extern inline int f ();`. However, if the first does not provide an external definition, where does it receive it? – user18348324 Mar 02 '23 at 03:38
  • The first declaration is already a declaration with external linkage, as that is the default when no other is specified for file-scope objects. As per the standard: "If the declaration of an identifier for an object has file scope and no storage-class specifier, its linkage is external." § 6.2.2. – markhc Mar 02 '23 at 04:41
  • Doesn't that contradict `An inline definition does not provide an external definition for the function` ? – user18348324 Mar 02 '23 at 06:51
  • I think here we start to have to choose our words very carefully. A declaration is not a definition. The inline definition does not provide an external definition because it is declared without extern in all TUs, but the declaration still has external linkage. To have an external definition as well as the inline, you need to define the function in at least one translation unit with the extern keyword. In any case, this is a very complex topic in which I don't claim to have any expertise, you'd probably do well to read other SO questions about this. – markhc Mar 02 '23 at 14:18
  • I did read them, and none answered it, which is why I asked here. – user18348324 Mar 02 '23 at 21:51
-1

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.

0___________
  • 60,014
  • 4
  • 34
  • 74
  • None of my questions were answered. Please re-read my question. – user18348324 Mar 02 '23 at 00:44
  • Basically `inline` keyword provides more mess as it makes code harder to compile and it can be ignored by the compiler. If you want to force inlining you must use not portable compiler extension or attributes – 0___________ Mar 02 '23 at 00:47