web-dev-qa-db-fra.com

Une fonction récursive peut-elle être intégrée?

inline int factorial(int n)
{
    if(!n) return 1;
    else return n*factorial(n-1);
}

En lisant this , j'ai trouvé que le code ci-dessus conduirait à une "compilation infinie" s'il n'était pas géré correctement par le compilateur.

Comment le compilateur décide-t-il d'inclure ou non une fonction?

130
Ashwin

Tout d'abord, la spécification inline sur une fonction n'est qu'un indice. Le compilateur peut (et le fait souvent) ignorer complètement la présence ou l'absence d'un qualificatif inline. Cela dit, un compilateur peut inline une fonction récursive, tout comme il peut dérouler une boucle infinie. Il lui suffit de limiter le niveau auquel il "déroulera" la fonction.

Un compilateur d'optimisation pourrait transformer ce code:

inline int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    return factorial(x);
}

dans ce code:

int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    if (x <= 1)
    {
        return 1;
    }
    else
    {
        int x2 = x - 1;
        if (x2 <= 1)
        {
            return x * 1;
        }
        else
        {
            int x3 = x2 - 1;
            if (x3 <= 1)
            {
                return x * x2 * 1;
            }
            else
            {
                return x * x2 * x3 * factorial(x3 - 1);
            }
        }
    }
}

Dans ce cas, nous avons essentiellement intégré la fonction 3 fois. Certains compilateurs do effectuent cette optimisation. Je me souviens que MSVC++ avait un paramètre pour régler le niveau d'inline qui serait effectué sur les fonctions récursives (jusqu'à 20, je crois).

133
Derek Park

En effet, si votre compilateur n'agit pas intelligemment, il peut essayer d'insérer des copies de votre fonction inlined de manière récursive, créant un code infiniment grand. La plupart des compilateurs modernes le reconnaîtront cependant. Ils peuvent soit:

  1. Pas du tout en ligne la fonction
  2. Insérez-le jusqu'à une certaine profondeur, et s'il n'est pas terminé d'ici là, appelez l'instance distincte de votre fonction à l'aide de la convention d'appel de fonction standard. Cela peut prendre en charge de nombreux cas courants de manière hautement performante, tout en laissant un repli pour le cas rare avec une grande profondeur d'appel. Cela signifie également que vous conservez à la fois des versions intégrées et séparées du code de cette fonction.

Pour le cas 2, de nombreux compilateurs ont #pragmas vous pouvez définir pour spécifier la profondeur maximale à laquelle cela doit être effectué. Dans gcc , vous pouvez également le transmettre depuis la ligne de commande avec --max-inline-insns-recursive (voir plus d'informations ici ).

23
Matt J

AFAIK GCC effectuera l'élimination des appels de queue sur les fonctions récursives, si possible. Cependant, votre fonction n'est pas récursive.

7
leppie

Le compilateur crée un graphe d'appel; lorsqu'un cycle est détecté en appelant lui-même, la fonction n'est plus insérée après une certaine profondeur (n = 1, 10, 100, quel que soit le compilateur réglé).

6
Paul Nathan

Certaines fonctions récursives peuvent être transformées en boucles, ce qui les ajuste effectivement à l'infini. Je crois que gcc peut le faire, mais je ne connais pas les autres compilateurs.

3
alex strange

Voir les réponses déjà données pour savoir pourquoi cela ne fonctionne généralement pas.

En tant que "note de bas de page", vous pouvez obtenir l'effet que vous recherchez (au moins pour la factorielle que vous utilisez comme exemple) en utilisant métaprogrammation du modèle . Collage de Wikipedia:

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};
2
yungchin

Le compilateur fera un graphe d'appel pour détecter ce genre de choses et les empêcher. Il verrait donc que la fonction s'appelle elle-même et non pas en ligne.

Mais il est principalement contrôlé par le mot-clé en ligne et les commutateurs du compilateur (par exemple, vous pouvez l'avoir de petites fonctions en ligne automatique même sans le mot-clé.) les appels que vous avez créés dans le code.

1
mattlant

"Comment le compilateur décide-t-il d'inclure ou non une fonction?"

Cela dépend du compilateur, des options spécifiées, du numéro de version du compilateur, de la quantité de mémoire disponible, etc.

Le code source du programme doit toujours obéir aux règles des fonctions intégrées. Que la fonction soit ou non en ligne, vous devez vous préparer à la possibilité qu'elle soit en ligne (un nombre de fois inconnu).

La déclaration de Wikipédia selon laquelle les macros récursives sont généralement illégales semble plutôt mal informée. C et C++ empêchent les invocations récursives mais une unité de traduction ne devient pas illégale en contenant du code macro qui semble avoir été récursif. Dans les assembleurs, les macros récursives sont généralement légales.

1
Windows programmer

Certains compilateurs (c'est-à-dire Borland C++) n'incluent pas de code en ligne qui contient des instructions conditionnelles (if, case, while etc.), de sorte que la fonction récursive de votre exemple ne serait pas insérée.

0
Roger Nelson