previo arriba siguiente contenido
Previo: Índice General Arriba: Manual de C Siguiente: 2. Principios de C
Regresar

Subsecciones

1. Compilación de un programa en C/C++

En esta capítulo se dan las procesos básicos que se requieren para compilar un programa de C. Se describe también el modelo de compilación de C y también como C soporta bibliotecas adicionales.

1.1 Creación, compilación y ejecución de un programa

1.1.1 Creación del programa

Se puede crear un archivo que contenga el programa completo, como en los ejemplos que se tienen más adelante. Se puede usar cualquier editor de textos ordinario con el que se este familiarizado. Un editor disponible en la mayoría de los sistemas UNIX es vi, y en Linux se puede usar pico.

Por convención el nombre del archivo debe terminar con ``.c'' por ejemplo: miprograma.c progprueba.c. El contenido del archivo deberá obedecer la sintaxis de C.

1.1.2 Compilación

Existen muchos compiladores de C. El cc es el compilador estándar de Sun. El compilador GNU de C es gcc, el cual es bastante popular y esta disponible en varias plataformas.

Existen también compiladores equivalentes de C++ los cuales usualmente son nombrados como CC. Por ejemplo, Sun provee CC y GNU GCC. El compilador de GNU es también denotado como g++.

Existen otros compiladores menos comunes de C y C++. En general todos los compiladores mencionados operan esencialmente de la misma forma y comparten muchas opciones comunes en la línea de opciones. Más adelante se listan y se dan ejemplos de opciones comunes de los compiladores. Sin embargo, la mejor referencia de cada compilador es a través de las páginas en línea, del manual del sistema. Por ejemplo: man gcc.

Para compilar el programa usaremos el comando gcc. El comando deberá ser seguido por el nombre del programa en C que se quiere compilar. Un determinado número de opciones del compilador pueden ser indicadas también. Por el momento no haremos uso de estas opciones todavía, se irán comentando algunas más esenciales.

Por lo tanto, el comando básico de compilación es:

      gcc programa.c
donde programa.c es el nombre del archivo.

Si hay errores obvios en el programa (tales como palabras mal escritas, caracteres no tecleados u omisiones de punto y coma), el compilador se detendrá y los reportará.

Podría haber desde luego errores lógicos que el compilador no podrá detectar. En el caso que esta fuera la situación se le estará indicando a la computadora que haga las operaciones incorrectas.

Cuando el compilador ha terminado con éxito, la versión compilada, o el ejecutable, es dejado en un archivo llamado a.out, o si la opción -o es usada con el compilador, el nombre después de -o es el nombre del programa compilado.

Se recomienda y es más conveniente usar la opción -o con el nombre del archivo ejecutable como se muestra a continuación:

      gcc -o programa programa.c
el cual pone el programa compilado en el archivo del programa señalado, en éste caso en programa, en vez del archivo a.out.

1.1.3 Ejecución del programa

El siguiente estado es correr el programa ejecutable. Para correr un ejecutable en UNIX, simplemente se escribe el nombre del archivo que lo contiene, en este caso programa (o a.out).

Con lo anterior, se ejecuta el programa, mostrando algún resultado en la pantalla. En éste estado, podría haber errores en tiempo de ejecución (run-time errors), tales como división por cero, o bien, podrían hacerse evidentes al ver que el programa no produce la salida correcta.

Si lo anterior sucede, entonces se debe regresar a editar el archivo del programa, recompilarlo, y ejecutarlo nuevamente.

1.2 El modelo de compilación de C

En la figura 1.1 se muestran las distintas étapas que cubre el compilador para obtener el código ejecutable.

Figura 1.1: Modelo de compilación de C.
\includegraphics[width=2in, clip]{figuras/modelo_C.eps}

1.3 El preprocesador

Esta parte del proceso de compilación será cubierta con más detalle en el capítulo 12 referente al preprocesador. Sin embargo, se da alguna información básica para algunos programas de C.

El preprocesador acepta el código fuente como entrada y es responsable de:

Por ejemplo:

1.4 Compilador de C

El compilador de C traduce el código fuente en código de ensamblador. El código fuente es recibido del preprocesador.

1.5 Ensamblador

El ensamblador crea el código fuentei o los archivos objeto. En los sistemas con UNIX se podrán ver los archivos con el sufijo .o.

1.6 Ligador

Si algún archivo fuente hace referencia a funciones de una biblioteca o de funciones que están definidas en otros archivos fuentes, el ligador combina estas funciones (con main()) para crear un archivo ejecutable. Las referencias a variables externas en esta étapa son resueltas.

1.7 Algunas opciones útiles del compilador

Descrito el modelo básico de compilación, se darán algunas opciones útiles y algunas veces esenciales. De nueva cuenta, se recomienda revisar las páginas de man para mayor información y opciones adicionales.

-E

Se compilador se detiene en la étapa de preprocesamiento y el resultado se muestra en la salida estándar.
gcc -E arch1.c

-c

Suprime el proceso de ligado y produce un archivo .o para cada archivo fuente listado. Después los archivos objeto pueden ser ligados por el comando gcc, por ejemplo:
gcc arch1.o arch2.o ... -o ejecutable

-lbiblioteca

Liga con las bibliotecas objeto. Esta opción deberá seguir los argumentos de los archivos fuente. Las bibliotecas objeto son guardadas y pueden estar estandarizadas, un tercero o usuario las crea. Probablemente la biblioteca más comúnmente usada es la biblioteca matemática (math.h). Esta biblioteca deberá ligarse explícitamente si se desea usar las funciones matemáticas (y por supuesto no olvidar el archivo cabecera #include <math.h>, en el programa que llama a las funciones), por ejemplo:
gcc calc.c -o calc -lm
Muchas otras bibliotecas son ligadas de esta forma.

-Ldirectorio

Agrega directorios a la lista de directorios que contienen las rutinas de la biblioteca de objetos. El ligador siempre busca las bibliotecas estándares y del sistema en /lib y /usr/lib. Si se quieren ligar bibliotecas personales o instaladas por usted, se tendrá que especificar donde estan guardados los archivos, por ejemplo:
gcc prog.c -L/home/minombr/mislibs milib.a

-Itrayectoria

Agrega una trayectoria o ruta a la lista de directorios en los cuales se buscarán los archivos cabecera #include con nombres relativos (es decir, los que no empiezan con diagonal /).

El procesador por default, primero busca los archivos #include en el directorio que contiene el archivo fuente, y después en los directorios nombrados con la opción -I si hubiera, y finalmente, en /usr/include. Por lo tanto, si se quiere incluir archivos de cabecera guardados en /home/minombr/miscabeceras se tendrá que hacer:
gcc prog.c -I/home/minombr/miscabeceras
Nota: Las cabeceras de las bibliotecas del sistema son guardados en un lugar especial (/usr/include) y no son afectadas por la opción -I. Los archivos cabecera del sistema y del usuario son incluídos en una manera un poco diferente.

-g

Opción para llamar las opciones de depuración (debug). Instruye al compilador para producir información adicional en la tabla de símbolos que es usado por una variedad de utilerías de depuración. Por ejemplo, si se emplea el depurador de GNU, el programa deberá compilarse de la siguiente forma para generar extensiones de GDB:
gcc -ggdb -o prog prog.c

-D

Define símbolos como identificadores (-Didentificador) o como valores (-Dsímbolo=valor) en una forma similar a la directiva del preprocesador #define).

-v

Muestra en la salida estandar de errores los comandos ejecutados en las étapas de compilación.

1.8 Uso de las bibliotecas

C es un lenguaje extremadamente pequeño. Muchas de las funciones que tienen otros lenguajes no están en C, por ejemplo, no hay funciones para E/S, manejo de cadenas o funciones matemáticas.

La funcionalidad de C se obtiene a través de un rico conjunto de bibliotecas de funciones.

Como resultado, muchas implementaciones de C incluyen bibliotecas estándar de funciones para varias finalidades. Para muchos propósitos básicos estas podrían ser consideradas como parte de C. Pero pueden variar de máquina a máquina.

Un programador puede también desarrollar sus propias funciones de biblioteca e incluso bibliotecas especiales de terceros, por ejemplo, NAG o PHIGS.

Todas las bibliotecas (excepto E/S estándar) requieren ser explícitamente ligadas con la opción -l y, posiblemente con L, como se señalo previamente.

1.9 Ejemplos

1.9.1 Creación de una biblioteca estática

Si se tiene un conjunto de rutinas que se usen en forma frecuente, se podría desear agruparlas en un conjunto de archivos fuente, compilar cada archivo fuente en un archivo objeto, y entonces crear una biblioteca con los archivos objeto. Con lo anterior se puede ahorrar tiempo al compilar en aquellos programas donde sean usadas.

Supongamos que se tiene un conjunto de archivos que contengan rutinas que son usadas frecuentemente, por ejemplo un archivo cubo.c:

float cubo(float x)
{
	return (x*x*x);
}

y otro archivo factorial.c

int factorial(int n)
{
	int i, res=1;
	for(i=1; i<=n; i++)
		res*=i;
	return (res);
}

Para los archivos de nuestras funciones también se debe tener un archivo de cabezera, para que puedan ser usadas, suponiendo que se tiene el siguiente archivo libmm.h con el siguiente contenido:

extern float cubo(float);
extern int factorial(int);

El código que use la biblioteca que se esta creando podría ser:

/* Programa prueba.c */
#include "libmm.h"
#define VALOR 4

main()
{
	printf("El cubo de %d es %f\n",VALOR, cubo(VALOR) );
	printf("\t y su factorial es %d\n",factorial(VALOR) );
}

Para crear la biblioteca se deben compilar los archivos fuente, que lo podemos hacer de la siguiente forma:

$ gcc -c cubo.c factorial.c

Lo cual nos dejará los archivos cubo.o y factorial.o. Después se debe crear la biblioteca con los archivos fuentes, suponiendo que nuestra biblioteca se llame libmm.a, tendrás que hacerlo con el comando ar así:

$ ar r libmm.a cubo.o factorial.o

Cuando se actualiza una biblioteca, se necesita borrar el archivo anterior (libmm.a). El último paso es crear un índice para la biblioteca, lo que permite que el ligador pueda encontrar las rutinas. Lo anterior, lo hacemos con el comando ranlib, por lo que teclearemos ahora:

$ ranlib libmm.a

Los últimos dos pasos pudieron ser combinados en uno sólo, entonces hubieramos podido teclear:

$ar rs libmm.a cubo.o factorial.o

Ahora que ya tenemos la biblioteca, es conveniente que coloquemos nuestra biblioteca y el archivo cabezera en algún lugar apropiado. Supongamos que dejamos la biblioteca en ~/lib y el fichero cabezera en ~/include, debemos hacer lo siguiente:

$ mkdir ../include
$ mkdir ../lib
$ mv libmm.h ../include
$ mv libmm.a ../lib

Si llegarás a modificar la biblioteca, tendrías que repetir la última instrucción.

Se debe ahora compilar el archivo con la biblioteca, de la siguiente forma:

gcc -I../include -L../lib -o prueba prueba.c -lmm

1.9.2 Creación de una biblioteca compartida

Las ventajas que presentan las bibliotecas compartidas, es la reducción en el consumo de memoria, si son usadas por más de un proceso, además de la reducción del tamaño del código ejecutable. También se hace el desarrollo más fácil, ya que cuando se hace algún cambio en la biblioteca, no se necesita recompilar y reenlazar la aplicación cada vez. Se requiere lo anterior sólo si se modifico el número de argumentos con los que se llama una función o se cambio el tamaño de alguna estructura.

El código de la biblioteca compartida necesita ser independiente de la posición, para hacer posible que sea usado el código por varios programas. Para crear la biblioteca hacerlo de la siguiente forma:

$ gcc -c -fPIC cubo.c factorial.c

Para generar la biblioteca dinámica hacer lo siguiente:

$ gcc -shared -o libmm.so cubo.o factorial.o

No existe un paso para la indexación como ocurre en las bibliotecas estáticas.

Después habrá que mover la biblioteca dinámica a su directorio correspondiente (../lib) y proceder a compilar para que nuestro código use la biblioteca.

$ gcc -I../include -L../lib -o prueba prueba.c -lmm

Nos preguntamos que sucede si hay una biblioteca compartida (libmm.so) y una estática (libmm.a) disponibles. En este caso, el ligador siempre toma la compartida. Si se desea hacer uso de la estática, se tendrá que nombrar explícitamente en la línea de comandos:

$ gcc -I../include -L../lib -o prueba prueba.c libmm.a

Cuando se usan bibliotecas compartidas un comando útil es ldd, el cual nos informa que bibliotecas compartidas un programa ejecutable usa, a continuación un ejemplo:

$ ldd prueba
	libstuff.so => libstuff.so (0x40018000)
	libc.so.6 => /lib/i686/libc.so.6 (0x4002f000)
	/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

Como se ve en cada línea aparece el nombre de la biblioteca, el camino completo a la biblioteca que es usada, y donde en el espacio de direcciones virtuales la biblioteca esta mapeada.

Si ldd muestra como salida not found para alguna biblioteca, se van a tener problemas y el programa no podra ser ejecutado. Una forma de arreglarlo es buscar la biblioteca y colocarla en el lugar correcto para que el programa loader la encuentre, que siempre busca por default en lib y /usr/lib. Si se tienen bibliotecas en otro directorio, crear una variable de ambiente LD_LIBRARY_PATH y poner los directorios separados por ;.

1.10 Funciones de la biblioteca de UNIX

El sistema UNIX da un gran número de funciones de C. Algunas implementan operaciones de uso frecuente, mientras otras están muy especializadas en relación a su aplicación.

No reinvente la rueda. Se recomienda revisar si una función en alguna biblioteca existe, en vez de hacer la tarea de escribir su propia versión. Con lo anterior se reduce el tiempo de desarrollo de un programa. Ya que las funciones de la biblioteca han sido probadas, por lo que estarán corregidas, a diferencia de cualquiera que un programador pueda escribir. Lo anterior reducirá el tiempo de depuración del programa.

1.10.1 Encontrando información acerca de las bibliotecas.

El manual de UNIX tiene una entrada para todas las funciones disponibles. La documentación de funciones esta guardada en la sección 3 del manual, y hay muchas otras útiles que hacen llamadas al sistema en la sección 2. Si ya sabe el nombre de la función que se quiere revisar, puede leer la página tecleando:

man 3 sqrt

Si no sabe el nombre de la función, una lista completa esta incluida en la página introductoria de la sección 3 del manual. Para leerlo, teclee:

man 3 intro

Hay aproximadamente 700 funciones. El número tiende a incrementarse con cada actualización al sistema.

En cualquier página del manual, la sección de SYNOPSIS incluye información del uso de la función. Por ejemplo:

#include <time.h>
char *ctime(const time_t *timep);

Lo que significa que se debe tener

#include <time.h>

en el archivo del programa que hace el llamado a ctime. Y que la función ctime toma un apuntador del tipo time_t como un argumento, y regresa una cadena char *.

En la sección DESCRIPTION se da una pequeña descripción de lo que hace la función.

1.11 Ejercicios

  1. Escribe, compila y corre el siguiente programa. Modifica el programa para que incluyas tu nombre y la forma como hicistes lo anterior. Para iniciar un comentario usa /* y para terminarlo */:
    main()
    {
       int i;
    
       printf("\t Numero \t\t Cubo\n\n");
    
       for( i=0; i<=20; ++i)
          printf("\t %d \t\t\t %d \n",i,i*i*i );
    }
    

  2. El siguiente programa usa la biblioteca matemática. Tecléalo, compilalo y ejecutalo. Incluye con comentarios dentro del programa tu nombre y la forma como hicistes lo anterior.
    #include <math.h>
    
    main()
    {
        int i;
    
        printf("\tAngulo  \t\t Seno\n\n");
    
        for( i=0; i<=360; i+=15)
            printf("\t %d \t\t\t %f \n",i,sin((double) i*M_PI/180.0));
    }
    

  3. Busca en los directorios /lib y /usr/lib las bibliotecas que están disponibles. Manda 3 nombres de estáticas y 3 de compartidas.

  4. Ve en /usr/include que archivos de cabecera están disponibles.
  5. Supongamos que se tiene un programa en C el cual tiene la función principal en main.c y que se tienen otras funciones en los archivos input.c y output.c:


previo arriba siguiente contenido
Anterior: Índice General Subir: Manual de C Siguiente: 2. Principios de C   Índice General
Última modificación : 2005-08-12
Héctor Tejeda V
<- htejeda@fismat.umich.mx