Поведение двух встроенных функций, вызывающих друг друга в C - PullRequest
3 голосов
/ 08 апреля 2019

Я пытаюсь изучить более сложные аспекты C и написал это, когда экспериментировал с ключевым словом __inline__:

#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;
}

Я перешел к руководствам по GNU и обнаружил, что опция компилятора -Winline предупреждает, когда функцию, помеченную __inline__, нельзя заменить.

Я действительно ожидал предупреждения, но я скомпилировал эту программу с gcc -ansi -pedantic -Wall -Wextra -Winline -o test test.c, а предупреждений не было.

Когда я запускал программу, она распечатывала число целую кучу раз перед ошибкой сегментации, предположительно из-за превышения предела рекурсии.

У меня вопрос, как gcc ведет себя в подобных случаях? Если он встроен в функции, как он узнает, что столкнулся с рекурсивным вызовом между двумя функциями?

Заранее спасибо

Ответы [ 2 ]

5 голосов
/ 08 апреля 2019

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

GCC не включает какие-либо функции, когда не выполняет оптимизацию, если вы не укажете атрибут 'Always_inline' для функции

Поскольку вы компилируете без оптимизацииgcc даже не пытается встроить ваши функции, поэтому вы не получаете предупреждения, что это не было сделано.

Когда я компилирую ваш код с помощью -O -Winline, я получаю предупреждение, как и ожидалось:

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);
     ^~~~~~
2 голосов
/ 08 апреля 2019

Согласно тому, что я вижу в goldbolt , в этом случае компилятор достаточно умен, чтобы понять, что этот код эквивалентен бесконечному циклу (см. .L2 в коде ниже).Эта оптимизация возможна, когда рекурсивные функции имеют хвостовую рекурсию

Это не имеет ничего общего с __inline__.Фактически, если вы удалите ключевое слово __inline__, вы получите ту же оптимизацию и также код сборки для g и f, которые никогда не call ed.

     .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

Ниже сравнивается сборка, сгенерированная gcc с (справа) и без (слева) ключевым словом __inline__ для g и f.Как видите, main содержит точно такой же код.Разница лишь в том, что вы получаете дополнительный код для g и f. enter image description here

...