Universidad Carlos III de Madrid

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2012 - Enero 2013

12. Los errores y advertencias del compilador

El compilador es el programa que, dado un conjunto de ficheros de código, los traduce y genera un fichero listo para ejecutar. Pero para que esta traducción sea posible, el programa debe cumplir con los requisitos que impone el lenguaje de programación. El tipo de comprobaciones que se hacen en esta traducción depende tanto del lenguaje de programación como del propio compilador. Cuando se detecta en el programa algo que no está permitido por las normas del lenguaje, el compilador muestra un mensaje y se detiene la traducción.

Estos mensajes no explican en detalle la causa por la que aparecen, sino la anomalía puntual que ha detectado el compilador. Por este motivo, puede ser difícil identificar la verdadera razón detrás de un error mostrado por el compilador.

12.1. Errores y advertencias

El lenguaje de programación C está definido de tal forma que hay numerosas decisiones que el compilador adopta de forma automática ante la ausencia de información. En otras palabras, el compilador toma decisiones sobre cómo traducir el código que el programador no ha escrito en el código. Esto, en ciertas situaciones, es cómodo, pues hace que los programas puedan ser escritos más rápido, pero sólo programadores con experiencia suelen aprovechar esta característica. Lo más recomendable es utilizar una opción en el compilador para que notifique aquellos casos en los que está tomando decisiones implícitas. En el compilador gcc esta opción es -Wall.

El compilador muestra dos tipos de anomalías: errores y advertencias. Un error es una condición que impide la obtención del programa final. Hasta que no se solventen todos los errores, el compilador no puede terminar su trabajo. Los primeros errores que se muestran son los más fiables porque se intenta traducir todo el programa hasta el final, con lo que es posible que haya errores que se deriven de otro anterior. Por tanto, si se arreglan los primeros errores, es recomendable compilar de nuevo para ver si como consecuencia han desaparecido también otros errores posteriores.

Las advertencias son mensajes que muestra el compilador sobre situaciones especiales en las que se ha detectado una anomalía pero que, asumiendo ciertas condiciones, la traducción del programa continúa. Por tanto, la compilación de un programa no termina mientras tenga errores pero, sin embargo, se puede obtener un programa traducido aunque el compilador muestre advertencias. Cuando se comienza a programar en C y hasta que no se tenga un nivel de experiencia elevado en la codificación de aplicaciones complejas (numerosos ficheros de código) la recomendación es:

Compilar siempre con la opción -Wall y no considerar el programa correcto hasta que no se hayan eliminado todas las advertencias.

12.2. Mensajes más comunes del compilador

A continuación se describen los mensajes más comunes que muestra el compilador. Se ofrecen ejemplos de código para ilustrar las causas del error, pero debe tenerse en cuenta que cada programa tiene una estructura diferente, y por tanto, el error puede aparecer por causas de alto nivel diferentes.

  1. `variable' undeclared (first use in this function)

    Este es uno de los más comunes y a la vez más fáciles de detectar. El símbolo que se muestra al comienzo del mensaje se utiliza pero no ha sido declarado. Sólo se muestra la primera de las apariciones.

  2. warning: implicit declaration of function `...'

    Esta advertencia aparece cuando el compilador encuentra una función que se utiliza en el código y de la que no tienen ningún tipo de información hasta el momento. El compilador asume que la función devuelve un entero, y continúa.

    Estas advertencias son las que se recomienda eliminar antes de probar la ejecución del programa. Dependiendo de la situación, hay varias formas de eliminar este mensaje. El compilador necesita saber cómo se define la función que ha encontrado. Para ello, en un lugar anterior en el fichero se debe incluir, o la definición de la función o su prototipo (el tipo de dato que devuelve, seguido de su nombre, los paréntesis con la definición de los parámetros y un punto y coma).

    Si esta información (la definición de la función) se necesita en múltiples ficheros, entonces, en lugar de escribir la misma información en cada uno de ellos se recomienda incluir el prototipo en un fichero que es luego incluido en donde se necesite a través de la orden #include del pre-procesador. Recuerda que el código de una función sólo puede estar presente en un único fichero de código. Si el compilador se lo encuentra en más de un fichero, entonces se producirá un error por haber definido una función múltiples veces.

    También puede darse el caso de que la información que se solicita esté contenida en un fichero a incluir por tratarse de una función en una biblioteca. Para consultar si la función está en una de las bibliotecas estándar de C se recomienda utilizar el comando man seguido por el nombre de la función. Si es parte de una biblioteca, en la misma página de manual se muestra qué fichero hay que incluir en el código.

  3. warning: initialization makes integer from pointer without a cast

    Esta advertencia nos dice que hemos utilizado un puntero en un contexto en el que se espera un entero. Esto está permitido en C y el compilador sabe qué hacer para traducir el código, pero igualmente se notifica por si se quiere confirmar esta operación mediante un cast. El siguiente código muestra un ejemplo que produce esta advertencia.

    int main (void) 
    {
      char c = "\n";  /* ¿incorrecto? */
      return 0;
    }

    En la primera línea se asigna un puntero a letra (pues una cadena entrecomillada se almacena por debajo como una dirección de memoria, que es la que contiene la primera letra) a una letra. A pesar de que c está definida como una letra, el compilador la trata como un entero, lo cual es legal. La cadena de texto "\n" se toma como un puntero, y de ahí se deriva el mensaje de advertencia. Se está asignando un puntero a una variable que no lo es y que puede ser considerada como un entero, y además, no se está utilizando un cast.

    En la mayoría de los casos esta asignación es en realidad un error del programador y por tanto necesita ser corregido. Pero es posible que se quiera manipular un puntero como un número tal y como se muestra en el siguiente programa:

    int main (void) 
    {
      int value = "\n"; /* Asignación correcta */
      return 0;
    }

    Supongamos que en este caso el programador sí necesita almacenar ese puntero en una variable entera. El compilador igualmente imprime la advertencia. Para confirmar al compilador que realmente este es la asignación que se quiere hacer se puede utilizar un casting, esto es, poner entre paréntesis como prefijo de un símbolo el tipo de datos al que queremos transformarlo. Una vez añadido este prefijo, el compilador entiende que esta asignación es realmente lo que se quiere hacer y no se muestra la advertencia.

    int main (void) 
    {
      int value = (int)"\n"; /* Asignación correcta y confirmada por el programador 
                            mediante el casting */
      return 0;
    }
  4. dereferencing pointer to incomplete type

    Este error aparece cuando se utiliza un puntero a una estructura de datos para acceder a alguno de sus campos, pero el compilador no tiene información suficiente sobre esa estructura de datos. El siguiente programa muestra esta situación:

    struct btree * data;
    int main (void) 
    {
      data->size = 0;  /* Información incompleta */
      return 0;
    }

    La declaración de la variable data como puntero a la estructura btree es correcta. Para declarar un puntero a una estructura no se necesita la definición de la estructura. Pero en la primera línea de la función main ese puntero se utiliza para acceder a uno de los campos de esta estructura. Al no tener esa definición, el error se muestra en pantalla.

    La causa más probable de este error es que la definición de la estructura está en otro fichero, y debe estar presente antes de que un puntero se utilice para acceder a sus campos. Por este motivo, las definiciones de las estructuras se suelen agrupar en ficheros con extensión *.h que son incluidos en los ficheros de código.

  5. warning: control reaches end of non-void function

    Esta advertencia aparece cuando una función se ha declarado como que devuelve un resultado y no se incluye ningún comando return para devolver ese resultado. Por tanto, o se ha definido incorrectamente la función, o se ha olvidado este comando. El compilador igualmente genera el ejecutable y aunque la función no devuelva resultado, el compilador sí devuelve un resultado a la función que ha hecho la invocación.

  6. warning: unused variable `...'

    Esta advertencia la imprime el compilador cuando detecta que una variable ha sido declarada pero no se utiliza en ningún sitio. El mensaje desaparece borrando la declaración.

  7. undefined reference to `...'

    Este mensaje aparece cuando en el código se ha invocado una función que no está definida en ningún lugar. El compilador nos dice que hay una referencia a una función sin definición. Mira qué función falta y cerciórate de que su definición se compila.

  8. error: conflicting types for `...'

    Se han encontrado dos definiciones del prototipo de una función. Una es un prototipo (tipo del resultado, nombre, paréntesis con los parámetros y un punto y coma) y la otra es la definición con el código. La información en ambos lugares no es idéntica y por tanto existe un conflicto. El compilador te muestra en qué linea se ha encontrado el conflicto y la definición previa que ha causado la contradicción.

12.3. Error en la ejecución

La ejecución de programas en C puede terminar abruptamente cuando se detecta un error. Pero a diferencia de otros lenguajes de programación, cuando se produce un error, en lugar de imprimir un informe detallado sobre el lugar en el que se produjo la anomalía, los programas en C imprimen el escueto mensaje Segmentation fault. No hay información adicional sobre qué ha causado el error, y debes revisar el programa y su ejecución para corregirlo.

Bibliografía

[Gough05] Brian J. Gough. An Introduction to GCC. Network Theory Ltd. Copyright © 1995 Network Theory Ltd. Capítulo 13. Common Error Messages .