4

I am in the process of trying to learn C's more advanced aspects and wrote this when experimenting with the __inline__ keyword:

#include <stdio.h>


void f(int);
void g(int);


__inline__ void f(int egg)
{
    printf("f %d\n", egg);
    g(egg);
}


__inline__ void g(int egg)
{
    printf("g %d\n", egg);
    f(egg);
}


int main()
{
    f(123);

    return 0;
}

I went to the GNU manuals and found that the -Winline compiler option warns when a function marked __inline__ can't be substituted.

I was indeed expecting a warning, but I compiled this program with gcc -ansi -pedantic -Wall -Wextra -Winline -o test test.c and there were no warnings.

When I ran the program, it printed out the number a whole bunch of times before a segmentation fault, presumably due to the recursion limit being exceeded.

My question is, how does gcc behave in cases like that? If it does inline the functions, how does it know it hit a recursive call between two functions?

Thank you in advance

Saucy Goat
  • 1,587
  • 1
  • 11
  • 32
  • 3
    You can look at the output of the compiler: https://godbolt.org/z/XZ_0NN (Spoiler: endless loop) – mch Apr 08 '19 at 14:26
  • 1
    Perhaps I'm not understanding the question, but didn't you just find out how gcc behaves in this situation? – Mark Benningfield Apr 08 '19 at 14:27
  • Do you see the same behavior if you use the standard keyword, `inline`, instead of the implementation extension, `__inline__`? – John Bollinger Apr 08 '19 at 14:27
  • @mch Thank you for the resource, I had never come across that tool! – Saucy Goat Apr 08 '19 at 14:29
  • @MarkBenningfield I know the program successfully compiled, but I am not sure whether the functions are actually inlined or not. I will edit my question for clarification though :) – Saucy Goat Apr 08 '19 at 14:30
  • @SaucyGoat: why don't you simply use `inline`? Which gcc version is this? – vgru Apr 08 '19 at 14:32
  • @JohnBollinger Since I am specifying the ANSI standard, the keyword is `__inline__`. Your suggestion `inline` would work on C99, if I am not mistaken. – Saucy Goat Apr 08 '19 at 14:32
  • @Groo version 7.3.0 – Saucy Goat Apr 08 '19 at 14:34
  • 1
    No, @SaucyGoat, `__inline__` has never been a keyword of any version of standard C, and it is unlikely to be a keyword of any future version, as its form is reserved for implementation use. Do not confuse implementation extensions (such as `__inline__`) with standard features, and do not confuse studying the behavior of a specific implementation with studying the behavior required of all conforming implementations. – John Bollinger Apr 08 '19 at 14:37
  • @JohnBollinger in the mouche. I misread the docs at https://gcc.gnu.org/onlinedocs/gcc/Inline.html and used `__inline__` for function definitions instead of headers. Feel free to elaborate an answer so I can mark the question as answered. Thank you! – Saucy Goat Apr 08 '19 at 14:40
  • @mch -- a loop does not explain the segfault in the OPs case? – MFisherKDX Apr 08 '19 at 14:49
  • @MFisherKDX The segfault was a note from OP, the questions were `My question is, how does gcc behave in cases like that? If it does inline the functions, how does it know it hit a recursive call between two functions?`. – mch Apr 09 '19 at 06:55

2 Answers2

5

https://gcc.gnu.org/onlinedocs/gcc-7.4.0/gcc/Inline.html#Inline

GCC does not inline any functions when not optimizing unless you specify the ‘always_inline’ attribute for the function

Since you are compiling without optimization, gcc does not even try to inline your functions, hence you do not get a warning that it wasn't done.

When I compile your code with -O -Winline, I get a warning as expected:

inline.c: In function ‘main’:
inline.c:8:17: warning: inlining failed in call to ‘f’: call is unlikely and code size would grow [-Winline]
 __inline__ void f(int egg)
                 ^
inline.c:24:5: note: called from here
     f(123);
     ^~~~~~
Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • 4
    However, if you compile the code with `-O2 -Winline`, again you get no warning, and the code generated is an infinite loop with the bodies of both `f` and `g` inlined into `main` and no recursive calls. This is because both the call from `f` to `g` and the call from `g` to `f` are [tail recursive](http://wiki.c2.com/?TailRecursion) so GCC can flatten out the recursion and successfully inline both functions. – zwol Apr 08 '19 at 14:44
2

According to what I see in goldbolt this case the compiler is smart enough to understand that that code is equivalent to a endless loop (see .L2 in the code below). This optimization is possible when recursive functions are tail recursive

This has nothing to do with __inline__. In fact if you remove the __inline__ keyword you get the same optimization and also the assembly code for g and f, which are never called.

     .LC0:
            .string "f %d\n"
    .LC1:
            .string "g %d\n"
    main:
            sub     rsp, 8
    .L2:
            mov     esi, 123
            mov     edi, OFFSET FLAT:.LC0
            xor     eax, eax
            call    printf
            mov     esi, 123
            mov     edi, OFFSET FLAT:.LC1
            xor     eax, eax
            call    printf
            jmp     .L2

The following compare the assembly generated by gcc with (on the right) and withtout (on the left) __inline__ keyword for g and f. As you can see the main contains exactly the same code. The only difference is that you get additional code for g and f.enter image description here

Davide Spataro
  • 7,319
  • 1
  • 24
  • 36
  • 3
    It has to do with _inlining_, but not so much with the `inline` or `__inline__` keywords. The compiler is allowed to inline functions even if you don't ask it to. – zwol Apr 08 '19 at 14:46
  • With -O3, I'm getting an identical (fully inlined) code inside `
    :` both with and without removing `__inline__` from the source.
    – l.k Apr 09 '19 at 08:42