Menu

Errores comunes en C: los fallos de principiante y cómo corregirlos

Los errores que todo programador de C comete al menos una vez: punto y coma olvidado, = en lugar de ==, declaraciones implícitas, scanf sin &, comparar cadenas con ==, división entera, variables sin inicializar y desplazamiento por uno, cada uno con su síntoma, su causa y su solución.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Todo programador de C se topa con la misma lista corta de errores, normalmente en la primera semana y de vez en cuando una década después. Lo que hace que valga la pena catalogarlos es que la mayoría producen mensajes de error que no los describen o, peor aún, ningún mensaje.

Cada entrada de abajo es un síntoma, la causa detrás de él y la solución.

Punto y coma olvidado y la cascada

Síntoma: un muro de errores, todos reportados en líneas que se ven correctas.

program.c:6:5: error: expected ';' before 'printf'
program.c:7:5: error: expected declaration specifiers before 'return'

Causa: C termina las instrucciones con ;. Si olvidas uno, el compilador pega la línea siguiente a la actual y luego reporta la confusión en el punto donde el texto combinado deja de tener sentido, típicamente en la línea siguiente.

int main(void) {
    int x = 5      /* falta el punto y coma */
    printf("%d\n", x);
    return 0;
}

Solución: cuando aparece un montón de errores, corrige solo el primero y vuelve a compilar. Todo lo que viene después puede ser consecuencia. Y revisa siempre la línea anterior a la que nombra el compilador.

La misma cascada surge de una llave sin cerrar o de un comentario /* sin terminar, donde los errores pueden aterrizar decenas de líneas más allá.

Una nota extra: no lleva punto y coma después del encabezado de un if, for o while, ni después de la llave de cierre de una definición de función. if (x > 0); compila bien y no hace nada: el ; es todo el cuerpo.

Asignación en lugar de comparación

Síntoma: una condición que siempre es verdadera, o una variable que cambia misteriosamente.

int x = 5;
if (x = 10) {                 /* asigna 10, luego evalua 10 -> verdadero */
    printf("x es diez\n");    /* siempre imprime; x ahora vale 10 */
}

Causa: = asigna, == compara. El valor de una asignación es el valor asignado, así que if (x = 10) evalúa 10, que es distinto de cero, que es verdadero. El compilador lo acepta porque a veces es lo que la gente quiere decir.

Solución: usa == en toda condición y activa -Wall para que el compilador avise:

warning: suggest parentheses around assignment used as truth value

Si de verdad quieres una asignación dentro de una condición (algo común con while ((c = getchar()) != EOF)), los paréntesis extra lo dejan claro y silencian la advertencia.

Declaración implícita de una función

Síntoma:

warning: implicit declaration of function 'printf'
warning: implicit declaration of function 'malloc'

...seguido a veces de resultados extraños en tiempo de ejecución o de un error de enlazado.

Causa: el compilador encontró una llamada a una función de la que no tiene declaración. En los dialectos anteriores a C99 suponía que la función devolvía int y seguía adelante; en sistemas de 64 bits esa suposición trunca un puntero devuelto a 32 bits, que es la forma en que un <stdlib.h> faltante convierte a malloc en un fallo.

Solución: incluye la cabecera correcta.

FunciónCabecera
printf, scanf, fopen<stdio.h>
malloc, free, atoi, rand, exit<stdlib.h>
strlen, strcpy, strcmp<string.h>
sqrt, pow, fabs<math.h>
isdigit, toupper<ctype.h>
time<time.h>

Para tus propias funciones, esa misma advertencia significa que llamaste a una antes de definirla. Pon un prototipo arriba de main:

scanf sin el &

Síntoma: el programa falla al leer la entrada, o no lee nada y deja la variable sin cambios.

int age;
scanf("%d", age);     /* falta & : pasa el valor, no la direccion */

Causa: scanf escribe dentro de tu variable, así que necesita la dirección de la variable. Pasar age entrega la basura que hubiera en ella y scanf la trata como un lugar donde escribir, lo que suele ser un segfault.

Solución: un & antes de la variable, para todos los tipos excepto un array, que ya es una dirección:

Dos trampas vecinas de la misma familia: usa %lf para un double en scanf (ahí %f significa float, y escribir los bytes de un float dentro de un double lo deja mal) y acota siempre un %s con un ancho, %49s para un búfer de 50 bytes, o una palabra larga lo desbordará.

Mejor todavía: lee una línea completa con fgets y analízala, lo cual no puede desbordar y no deja entrada suelta en el búfer. Hay más sobre ambos en scanf.

Comparar cadenas con ==

Síntoma: dos cadenas que claramente coinciden se comparan como distintas.

char a[] = "hola";
char b[] = "hola";

if (a == b) {              /* compara dos direcciones: falso */
    printf("iguales\n");
}

Causa: una cadena de C no es un valor, es un puntero al primer carácter. == compara los punteros. Dos arrays que contienen el mismo texto viven en direcciones distintas, así que la prueba es falsa. (Para confundir más, comparar dos literales idénticos a veces da verdadero, porque el compilador puede guardar una sola copia, lo que hace que el error sea intermitente).

Solución: strcmp, y recuerda que devuelve 0 cuando son iguales:

La lectura invertida también engaña a mucha gente: if (strcmp(a, b)) es verdadero cuando las cadenas difieren, ya que un resultado distinto de cero significa "no son iguales". Escribe siempre el == 0 de forma explícita.

División entera

Síntoma: un promedio de 0, un porcentaje que siempre es 0 o 100, una razón que perdió su parte fraccionaria.

int correct = 7, total = 10;
double score = correct / total;      /* 0.0, no 0.7 */

Causa: ambos operandos son int, así que C hace división entera y trunca antes de que el resultado se asigne a un double. 7 / 10 es 0; convertir 0 a double es 0.0.

Solución: haz que uno de los operandos sea de punto flotante antes de la división:

Convertir un operando promueve el otro automáticamente. Convertir el resultado llega demasiado tarde: el truncamiento ya ocurrió. Consulta conversión de tipos.

La misma trampa se esconde en expresiones como (a + b) / 2 para un punto medio y 1 / 2 * x, que siempre es 0 sin importar cuánto valga x.

return faltante o equivocado

Síntoma: una función devuelve un número incorrecto pero verosímil, distinto en cada ejecución o en cada compilación.

int add(int a, int b) {
    int sum = a + b;
    /* sin instruccion return */
}

Causa: llegar al final de una función que no es void sin retornar da un valor no especificado: en la práctica, lo que hubiera quedado en el registro de retorno. Es comportamiento indefinido si quien llama lo usa.

La forma más traicionera retorna en algunos caminos y en otros no:

int classify(int n) {
    if (n > 0) return 1;
    if (n < 0) return -1;
    /* con n == 0 se cae por el final */
}

Solución: retorna en todos los caminos y compila con -Wall: el mensaje de GCC "control reaches end of non-void function" detecta ambas versiones.

Variables sin inicializar

Síntoma: salida basura, o resultados que cambian entre ejecuciones y entre niveles de optimización.

int total;                       /* contiene lo que hubiera en la pila */
for (int i = 1; i <= 5; i++) {
    total += i;                  /* sumando sobre basura */
}
printf("%d\n", total);           /* algun numero enorme */

Causa: las variables locales no se ponen en cero. Una variable global o static se inicializa a cero automáticamente; una local empieza con los bytes que ya estuvieran en esa dirección de la pila.

Solución: inicializa en el punto de la declaración. No cuesta nada y elimina toda esta clase de error:

-Wall -Wextra avisa de muchos de estos casos ("may be used uninitialized"), y -fsanitize=memory o valgrind atrapan el resto. Este es especialmente desagradable porque un puntero sin inicializar lleva directo a una falla de segmentación.

Desplazamiento por uno

Síntoma: se pierde el último elemento, o se toca un elemento de más y el programa se porta mal después.

int arr[5];
for (int i = 0; i <= 5; i++) {   /* toca arr[5], que no existe */
    arr[i] = i;
}

Causa: un array de n elementos tiene índices del 0 al n - 1. <= ejecuta una pasada de más.

Solución: el patrón i < n, y calcula n a partir del array en lugar de escribir el número dos veces:

La versión con cadenas (olvidar espacio para el '\0') es el mismo error con otro disfraz, y char word[5] con "hello" dentro es un desbordamiento de búfer.

Dos más pequeños que conviene conocer

Punto y coma después del encabezado de un bucle. for (int i = 0; i < 10; i++); seguido de un bloque entre llaves ejecuta el bucle diez veces sin hacer nada y luego ejecuta el bloque una vez. Compila sin quejarse.

sizeof sobre un puntero. Dentro de una función, un parámetro de tipo array es un puntero, así que sizeof(arr) es el tamaño del puntero (8 bytes), no el del array. Pasa la longitud como un argumento aparte:

El hábito que previene casi todo esto

Compila con las advertencias activadas, desde el primerísimo programa:

gcc -Wall -Wextra -g program.c -o program

-Wall -Wextra detecta la asignación dentro de una condición, el return faltante, la lectura sin inicializar, la variable sin usar y el especificador de printf que no coincide con su argumento. Añadir -fsanitize=address,undefined durante el desarrollo atrapa casi todo lo demás en el momento en que ocurre.

Trata cada advertencia como un error con el que todavía no te has topado. Un programa en C que compila sin advertencias no tiene garantizada su corrección, pero casi todo programa en C que falla estaba advirtiendo algo antes.

Preguntas frecuentes

¿Qué significa 'implicit declaration of function' en C?

El compilador encontró una llamada a una función que nunca vio declarada. Casi siempre olvidaste un #include: printf necesita <stdio.h>, malloc necesita <stdlib.h>, strlen necesita <string.h>. También puede significar que llamaste a una función tuya antes de definirla, lo que se arregla con un prototipo arriba de main.

¿Por qué mi programa en C reporta un error en una línea que se ve bien?

Normalmente porque el error real está en la línea anterior. Un punto y coma que falta, una llave sin cerrar o un comentario sin terminar hacen que el compilador lea tus dos líneas como una sola, así que reporta la confusión donde finalmente deja de poder analizarse. Revisa siempre primero la línea de arriba de la reportada.

¿Por qué no puedo comparar cadenas con == en C?

Porque una cadena de C es un char *, un puntero, así que == compara dos direcciones, no los caracteres a los que apuntan. Dos cadenas idénticas guardadas en lugares distintos dan false. Usa strcmp(a, b) == 0 de <string.h>, que devuelve 0 cuando el contenido coincide.

¿Por qué 5 / 2 da 2 en C?

Porque ambos operandos son enteros, así que C hace división entera y descarta la parte fraccionaria. Haz que uno de los lados sea un valor de punto flotante para obtener 2.5: 5.0 / 2, o (double)a / b cuando trabajas con variables. Convertir el resultado llega demasiado tarde: (double)(5 / 2) es 2.0.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR