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.
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.
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.cdonde 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.cel cual pone el programa compilado en el archivo del programa señalado, en éste caso en programa, en vez del archivo a.out.
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.
En la figura 1.1 se muestran las distintas étapas que cubre el compilador para obtener el código ejecutable.
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:
#include
-- incluye el contenido del archivo nombrado. Estos
son usualmente llamados archivos de cabecera (header). Por ejemplo:
#include <math.h>
-- Archivo de la biblioteca estándar de matemáticas.
#include <stdio.h>
-- Archivo de la biblioteca estándar
de Entrada/Salida.
#define
-- define un nombre simbólico o constante.
Sustitución de macros.
#define TAM_MAX_ARREGLO 100
El compilador de C traduce el código fuente en código de ensamblador. El código fuente es recibido del preprocesador.
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
.
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.
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 comandogcc
, 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:Muchas otras bibliotecas son ligadas de esta forma.gcc calc.c -o calc -lm
-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:Nota: Las cabeceras de las bibliotecas del sistema son guardados en un lugar especial (gcc prog.c -I/home/minombr/miscabeceras
/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 (-D
identificador) o como valores (-D
sí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.
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.
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
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 ;
.
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.
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.
/*
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 ); }
#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)); }
/lib
y /usr/lib
las bibliotecas que están disponibles. Manda 3 nombres de estáticas y 3 de compartidas.
man
para obtener detalles de las funciones de la biblioteca.
ar tv biblioteca
/usr/include
que archivos de cabecera están disponibles.
more
o cat
para ver los archivos de
texto.
main.c
y que se tienen otras funciones en los archivos
input.c
y output.c
:
proceso1
guardada en el directorio estándar de
las bibliotecas del sistema?
proceso2
guardada en tu directorio casa?
header
de su directorio casa y también en directorio de
trabajo actual. ¿Cómo se modificarían los comandos de compilación
para hacer lo señalado?