4

I'm trying to compile the following code with clang at various optimization levels:

#include <stdio.h>

inline int foo() { return 42; }

int main() {
    printf("%d\n", foo());
}

At -O1, -O2, -O3, and -Os, it compiles successfully, but it fails when using -O0:

$ clang -O0 -o main main.c
Undefined symbols for architecture x86_64:
  "_foo", referenced from:
      _main in main-8b9319.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

The failure at -O0 (and workarounds) can be explained by Clang's inline compatibility, but then I'd naively expect this to fail regardless of optimization level. It appears that some optimizations enabled at -O1 and above are doing something to prevent this link error from happening, but I'm curious as to which optimizations they are and why they seem to have different semantics than using inline alone at -O0.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Jeff Ames
  • 1,424
  • 10
  • 18

1 Answers1

5

At -O1 and greater it is not calling the function it just moves the code into main, we can see this by using godbolt which shows the asm as follows see it live:

main:                                   # @main
  pushq %rax
  movl  $.L.str, %edi
  movl  $42, %esi
  xorl  %eax, %eax
  callq printf
  xorl  %eax, %eax
  popq  %rdx
  retq

Which is what the reference says:

[...]because if add isn't inlined (for example, when compiling without optimization), then main will have an unresolved reference to that other definition[...]

this is covered in draft C99 standard section 6.7.4 Function specifiers:

Any function with internal linkage can be an inline function. For a function with external linkage, the following restrictions apply: 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. An inline definition provides an alternative to an external definition, which a translator may use to implement any call to the function in the same translation unit. It is unspecified whether a call to the function uses the inline definition or the external definition.122)

this site has a good explanation of the language: The tricky inline specifier in C99

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • That's exactly what to expect with an `inline` function. As noted, it does not get inlined with `-O0` (and also not compiled). [See that one live](http://gcc.godbolt.org/#%7B%22version%22%3A3%2C%22filterAsm%22%3A%7B%22labels%22%3Atrue%2C%22directives%22%3Atrue%2C%22commentOnly%22%3Atrue%2C%22colouriseAsm%22%3Atrue%7D%2C%22compilers%22%3A%5B%7B%22sourcez%22%3A%22MQSwdgxgNgrgJgUwAQB4DOAXOID2A6ACwD4AoE8KcZcDJAMxxwAoBKJAbyQCcEMYuwSACwAmANxIAvmRpIAtgENwrDiSTqkABy406TAEQBSOAB0w%2BgDT1GrFmJKSgA%3D%3D%22%2C%22compiler%22%3A%22clang37x%22%2C%22options%22%3A%22-x%20c%20-O0%20-fverbose-asm%22%7D%5D%7D) – Jongware May 28 '15 at 20:33
  • 1
    @Jongware yes, I spend so much time in C++ land I needed to reread this part of C99 carefully. Ok, TIL something new. – Shafik Yaghmour May 28 '15 at 20:55
  • I think solution #2 in your "The tricky inline specifier" link is wrong: having `extern inline void foo() { body }` in a header would cause multiple external definitions if the header is used in two different TUs. The correct setup is to have `inline void foo() { body }` in the header, and `extern inline void foo();` in exactly one TU. – M.M May 29 '15 at 02:34
  • @MattMcNabb I think the recommendation is meant to mean something closer to what the [clang docs](http://clang.llvm.org/compatibility.html#inline) says in bullet 3 but it is not clear. – Shafik Yaghmour May 29 '15 at 02:41
  • 1
    @ShafikYaghmour agree that it's unclear. That bullet point should also mention that `extern inline int add();` is sufficient, it's not necessary or even desirable to repeat the function body (or the argument list) elsewhere. – M.M May 29 '15 at 02:48