2008-10-10 11 views
125
inline int factorial(int n) 
{ 
    if(!n) return 1; 
    else return n*factorial(n-1); 
} 

Como estaba leyendo this, encontré que el código anterior conduciría a una "compilación infinita" si el compilador no lo maneja correctamente.¿Puede una función recursiva estar en línea?

¿Cómo decide el compilador si desea alinear una función o no?

Respuesta

129

En primer lugar, la especificación inline 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 calificador inline. Dicho esto, un compilador puede alinear una función recursiva, del mismo modo que puede desenrollar un bucle infinito. Simplemente tiene que establecer un límite en el nivel al que "desenrollará" la función.

Un compilador de optimización podría convertir 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, hemos básicamente la función inline 3 veces. Algunos compiladores hacen realizar esta optimización. Recuerdo que MSVC++ tenía una configuración para ajustar el nivel de alineación que se realizaría en las funciones recursivas (hasta 20, creo).

+19

es #pragma inline_recursion (activado). La documentación sobre la profundidad máxima no es consistente ni concluyente. Los valores 8, 16 o el valor de #pragma inline_depth son posibles. – peterchen

+0

@peterchen Si la función inlined está cambiando el valor de uno de sus argumentos, suceda, creo que es mejor alinear la función dentro de hecho en lugar de main. Perdón por mi inglés – obo

+1

@obounaim: Podrías pensar eso. MSVC no. – SecurityMatt

1

El compilador creará 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 tenerlo en línea incorporando pequeñas funciones incluso sin la palabra clave.) Es importante tener en cuenta que las compilaciones de depuración nunca deberían estar en línea ya que la callstack no conservarse para reflejar las llamadas que creó en el código.

19

De hecho, si su compilador no actúa de forma inteligente, puede tratar de insertar copias de su función inline d recursivamente, creando código infinitamente grande. La mayoría de los compiladores modernos reconocerán esto, sin embargo. Se puede:

  1. no coloca en línea de la función en absoluto
  2. en línea que hasta una cierta profundidad, y si no ha terminado para entonces, llamar a la instancia independiente de su función usando la función estándar de convención de llamada . Esto puede ocuparse de muchos casos comunes de una manera de alto rendimiento, mientras que deja una alternativa para el caso raro con una gran profundidad de llamada. Esto también significa que tiene guardadas versiones impresas y separadas del código de esa función.

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

4

El compilador crea un gráfico de llamadas; cuando se detecta un ciclo invocando a sí mismo, la función ya no se inline después de cierta profundidad (n = 1, 10, 100, cualquiera que sea el sintonizador del compilador).

1

"¿Cómo decide el compilador si alinea 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.

código fuente del programa todavía tiene que obedecer las reglas para funciones inline . Ya sea que la función esté incorporada o no, debe prepararse para la posibilidad de que esté en línea (un número desconocido de veces).

La declaración de Wikipedia de que las macros recursivas son típicamente ilegales parece bastante poco informada. C y C++ evitar las invocaciones recursivas, pero una unidad de traducción no se vuelve ilegal al contener un código macro que parece que habría sido recursivo. Las macros ve son típicamente legales.

1

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

Como "nota al pie", puede lograr el efecto que está buscando (al menos para el factorial que está utilizando como ejemplo) usando template metaprogramming. Pegar de Wikipedia:

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

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

Eso es muy bonito, pero tenga en cuenta que la publicación original tenía un argumento variable "int n". –

+1

Es cierto, pero también tiene poco sentido querer "recursivo reclinado" cuando n no se conoce en tiempo de compilación ... ¿cómo podría el compilador lograrlo? 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 insertar dos veces, recurse n >> 2 veces y obtendrá 2 + 2 devoluciones del código resultante. – MSalters

7

yo sepa GCC hará eliminación llamada de cola en las funciones recursivas, si es posible. Sin embargo, su función no es recursiva de cola.

3

Algunas funciones recursivas se pueden transformar en bucles, lo que las alinea infinitamente de forma efectiva. Creo que gcc puede hacer esto, pero no sé sobre otros compiladores.

0

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

Cuestiones relacionadas