Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Regla 0x2017h: Toda funcion recursiva debe tener un caso base explicito

Funciones, contratos y modularizacion (0x20XX)

Universidad Nacional de Río Negro

0x2017h: Toda funcion recursiva debe tener un caso base explicito

Enunciado normativo

Toda funcion recursiva DEBE declarar un caso base explicito y alcanzable que detenga la recursion.

Síntoma en el código del estudiante

Una función que se invoca a sí misma sin ninguna condición que detenga la cadena, o con una condición que nunca se alcanza porque el argumento no avanza hacia ella. El programa compila sin errores y aborta en ejecución con SIGSEGV.

Diagnóstico

Mecanismo del defecto

Cada llamada a una función reserva un marco de activación en la pila con sus parámetros y su dirección de retorno. Sin un caso base, la función se llama indefinidamente y la pila crece hasta agotar el espacio reservado por el sistema. En ese momento el proceso recibe una violación de segmento. El return que “debería” cortar nunca se alcanza porque falta la guarda que lo proteja, o porque el argumento no decrece.

Esto conecta con el uso correcto de funciones que exige 0x2008h: Los ejercicios deben ser resueltos mediante funciones: una función debe tener un contrato completo, y su caso base es parte de él.

Consecuencia observable

El depurador muestra una pila de miles de marcos idénticos apilados. El sistema reporta Segmentation fault (core dumped), y la regla 0x2008h: Los ejercicios deben ser resueltos mediante funciones se complementa con 0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda (la guarda de entrada es el caso base).

Fundamento en el estándar C11

El estándar no fija un tamaño de pila; deja esa implementación al entorno (§5.2.4.1 enumera los límites mínimos de traducción, no de ejecución). Por eso la recursión sin corte no es un error de compilación sino un defecto de ejecución con comportamiento indefinido de facto: el proceso termina de forma anormal. La norma exige que cada llamada retorne para que el flujo sea predecible (§6.9.1).

Corrección idiomática

❌ Código con el antipatrón

void cuenta(int n)
{
    cuenta(n - 1);
}

✅ Código refactorizado

void cuenta(int n)
{
    if (n <= 0) {
        return;
    }

    cuenta(n - 1);
}

La guarda inicial corta la recursión y el argumento avanza hacia el caso base.

Errores típicos al compilar o ejecutar

$ ./programa
Segmentation fault (core dumped)

$ gdb ./programa
#0  cuenta (n=-31234) at cuenta.c:4
#1  cuenta (n=-31233) at cuenta.c:4
#2  cuenta (n=-31232) at cuenta.c:4
... miles de marcos idénticos ...

Checklist de verificación

Reglas relacionadas

Síntoma en el código del estudiante

Dos o más funciones que se llaman entre sí en ciclo, sin que ninguna incluya una condición de corte. El programa compila y falla en ejecución con Segmentation fault.

Diagnóstico

Mecanismo del defecto

La recursión mutua reparte el estado entre varias funciones, de modo que la condición de corte debe vivir en al menos una de ellas. Si el estudiante “traduce” el ciclo de llamadas sin escribir el caso base, la pila acumula marcos de fa y fb alternados hasta agotarse. El error es especialmente difícil de ver porque ninguna función es recursiva por sí sola: el ciclo aparece recién en el grafo de llamadas.

Modificar un parámetro por valor dentro del ciclo (por ejemplo n--) agrava el problema, porque oculta que el argumento no se está pasando hacia el corte. La regla 0x2010h: Prohibición de reasignar o modificar parámetros recibidos por valor dentro de la función exige conservar el parámetro y pasar la expresión modificada; combinada con la guarda de 0x2001h: Las funciones deben usar cláusulas de guarda y retornos anticipados para reducir la anidación profunda, da una recursión terminante.

Consecuencia observable

El depurador muestra marcos alternados de las dos funciones. El sistema reporta Segmentation fault (core dumped), y el análisis del grafo de llamadas detecta el ciclo.

Fundamento en el estándar C11

La pila de ejecución y su tamaño son responsabilidad de la implementación (§5.2.4.1 fija límites mínimos de traducción, no de ejecución). Una recursión sin corte consume el recurso hasta la falla. El estándar garantiza el retorno de cada llamada (§6.9.1); sin caso base, ese retorno no ocurre y el programa termina de forma anormal.

Corrección idiomática

❌ Código con el antipatrón

void fa(int n) { fb(n); }
void fb(int n) { fa(n); }

✅ Código refactorizado

void fb(int n);

void fa(int n)
{
    if (n <= 0) {
        return;
    }

    fb(n - 1);
}

void fb(int n)
{
    if (n <= 0) {
        return;
    }

    fa(n - 1);
}

Cada función del ciclo tiene su caso base y delega con un argumento que avanza hacia él, de modo que la profundidad de la pila queda acotada por n.

Errores típicos al compilar o ejecutar

$ ./programa
Segmentation fault (core dumped)

$ gdb ./programa
#0  fa (n=-99999) at ciclo.c:2
#1  fb (n=-99998) at ciclo.c:3
#2  fa (n=-99999) at ciclo.c:2
... marcos alternados hasta agotar la pila ...

Checklist de verificación

Reglas relacionadas