¿Puede una función recursiva estar en línea?

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

Mientras leía esto , descubrí que el código anterior conduciría a una "compilación infinita" si el compilador no lo maneja correctamente.

¿Cómo decide el compilador si en línea una función o no?

Ashwin
fuente

Respuestas:

137

Primero, la inlineespecificación de una función es solo una pista. El compilador puede (y a menudo lo hace) ignorar por completo la presencia o ausencia de un inlinecalificador. Dicho esto, un compilador puede en línea una función recursiva, tanto como puede desenrollar un bucle infinito. Simplemente tiene que colocar un límite en el nivel al que "desenrollará" la función.

Un compilador optimizador podría activar este código:

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

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

en este código:

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

En este caso, básicamente hemos incorporado la función 3 veces. Algunos compiladores hacen realizar esta optimización. Recuerdo que MSVC ++ tenía una configuración para ajustar el nivel de línea que se realizaría en funciones recursivas (hasta 20, creo).

Derek Park
fuente
20
es #pragma inline_recursion (activado). La documentación sobre la profundidad máxima no es consistente o no es concluyente. Los valores 8, 16 o el valor de #pragma inline_depth son posibles.
Peter
@peterchen Si la función en línea está cambiando el valor de uno de sus argumentos, lo que sucede, creo que es mejor alinear la función dentro del hecho en lugar de main. Lo siento por mi inglés
ob_dev
1
@obounaim: Puedes pensar eso. MSVC no lo hace.
SecurityMatt
23

De hecho, si su compilador no actúa de manera inteligente, puede intentar insertar copias de su inlinefunción d de forma recursiva, creando un código infinitamente grande. Sin embargo, la mayoría de los compiladores modernos lo reconocerán. Pueden:

  1. No en línea la función en absoluto
  2. Inclínelo hasta una cierta profundidad, y si no ha terminado para entonces, llame a la instancia separada de su función utilizando la convención de llamada de función estándar. Esto puede encargarse de muchos casos comunes de una manera de alto rendimiento, mientras deja un respaldo para el caso raro con una gran profundidad de llamada. Esto también significa que mantiene las versiones en línea y separadas del código de esa función.

Para el caso 2, muchos compiladores tienen #pragmas que puede configurar para especificar la profundidad máxima a la que se debe hacer esto. En gcc , también puede pasar esto desde la línea de comandos con --max-inline-insns-recursive(vea más información aquí ).

Matt J
fuente
7

AFAIK GCC hará la eliminación de llamadas de cola en funciones recursivas, si es posible. Sin embargo, su función no es recursiva de cola.

leppie
fuente
6

El compilador crea un gráfico de llamadas; cuando se detecta un ciclo que se llama a sí mismo, la función ya no está en línea después de una cierta profundidad (n = 1, 10, 100, cualquiera que sea el compilador sintonizado).

Paul Nathan
fuente
3

Algunas funciones recursivas se pueden transformar en bucles, que efectivamente los alinea infinitamente. Creo que gcc puede hacer esto, pero no sé sobre otros compiladores.

alex extraño
fuente
2

Vea las respuestas ya dadas por qué esto no suele funcionar.

Como una "nota al pie", puede lograr el efecto que está buscando (al menos para el factorial que está usando como ejemplo) usando la metaprogramación de plantilla . Pegando de Wikipedia:

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

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};
yungchin
fuente
1
Eso es muy lindo, pero tenga en cuenta que la publicación original tenía un argumento variable "int n".
Programador de Windows
1
Es cierto, pero también tiene poco sentido querer "línea recursiva" cuando no se conoce n en el momento de la compilación ... ¿cómo podría el compilador lograr eso? Entonces, en el contexto de la pregunta, creo que esta es una alternativa relevante.
yungchin
1
Vea el ejemplo de Derek Park sobre cómo hacerlo: al alinearse dos veces, recurre n >> 2 veces y obtiene 2 + 2 retornos del código resultante.
MSalters
1

El compilador hará un gráfico de llamadas para detectar este tipo de cosas y evitarlas. Entonces vería que la función se llama a sí misma y no en línea.

Pero principalmente está controlado por la palabra clave en línea y los modificadores del compilador (por ejemplo, puede tener funciones pequeñas en línea automáticamente incluso sin la palabra clave). Es importante tener en cuenta que las compilaciones de depuración nunca deben estar en línea ya que la pila de llamadas no se conservará en espejo Las llamadas que creó en código.

mate
fuente
1

"¿Cómo decide el compilador si en línea una función o no?"

Eso depende del compilador, las opciones que se especificaron, el número de versión del compilador, tal vez la cantidad de memoria disponible, etc.

El código fuente del programa todavía tiene que obedecer las reglas para las funciones en línea. Independientemente de si la función se integra o no, debe prepararse para la posibilidad de que se incorpore (algunas veces se desconoce).

La declaración de Wikipedia de que las macros recursivas son típicamente ilegales parece poco informada. C y C ++ evitan las invocaciones recursivas, pero una unidad de traducción no se vuelve ilegal al contener código macro que parece que hubiera sido recursivo. En los ensambladores, las macros recursivas suelen ser legales.

Programador de Windows
fuente
0

Algunos compiladores (es decir, Borland C ++) no incluyen código en línea que contenga sentencias condicionales (if, case, while, etc.), por lo que la función recursiva en su ejemplo no estaría en línea.

Roger Nelson
fuente