Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


1.4.0  Construir un ejecutable (compilación)

 

"For all intent and purpose, any description of what the codes are doing should be construed as being a note of what we thought the codes did on our machine on a particular Tuesday of last year. If you're really lucky, they might do the same for you someday. Then again, do you really feel *that* lucky?".   R. Freund. "Readme" de una subrutina matemática (tomado de las FAQ de gnuplot).

§1  Sinopsis

En el presente Curso C++ nos referimos infinidad de veces al "Compilador". Sin embargo, hablando en propiedad no existe realmente tal cosa; en realidad los procesos anteriormente descritos ( 1.4) se ejecutan por una serie de aplicaciones distintas:  El preprocesador, el analizador sintáctico, el generador de código, el enlazador y algunas otras auxiliares, aunque de forma genérica nos referimos a ellas como "el compilador". Además, los "compiladores" ofrecen una serie de herramientas adicionales que no son propiamente para construir ejecutables, sino para labores auxiliares como puede ser la construcción de librerías.  Estas herramientas son conocidas colectivamente como binutils.

Nota: en el caso del "Compilador" Borland C++, se dispone de los módulos que siguen, cada uno de los cuales realiza una labor concreta:  ILINK32 EXE,  BCC32 EXE,  BRC32 EXE,  BRCC32 EXE,  COFF2OMF EXE,  CPP32 EXE,  FCONVERT EXE,  GREP EXE,  IMPDEF EXE,  IMPLIB EXE,  MAKE EXE,  TDUMP EXE,  TLIB EXE,  TOUCH EXE,  TRIGRAPH EXE, TD32 EXE  (ver detalles 1.4.0w1).  En el caso del compilador GNU para C++, además del compilador propiamente dicho g++, están el enlazador ld; el intérprete de comandos make, y una serie de utilidades auxiliares (binutils) tales como ar, nm, objcopy, objdump, ranlib, readelf, size, windres, etc.


Para construir el ejecutable es necesario cubrir sucesivamente diversas etapas utilizando los módulos adecuados, y los resultados de unos como entradas de los siguientes. Sin embargo, para facilitar el proceso, los "compiladores" cuentan con una utilidad a la que se conoce genéricamente como compilador pero que en realidad es un programa supervisor ("Front-end") que se encarga de invocar los módulos sucesivos en el orden correcto y con los parámetros adecuados al fin que se persigue.  En el caso del "Compilador" Borland C++ es BCC32.exe y CL.exe en MS Visual C++. En el caso del "Compilador" GNU C++, el programa supervisor es gcc, que actúa como front-end del preprocesador (cpp), que actúa a su vez como front-end generador de código y este del enlazador (ld).

Lo corriente es que el programa supervisor acepte una larga fila de parámetros que controlan su funcionamiento, dado que él mismo debe controlar una serie de procesos (en el caso de Bcc32, la lista asciende a más de 135 opciones distintas). Viene a ser para el programador una especie de navaja suiza, la herramienta utilizada habitualmente. Solo en casos excepcionales se utilizan directamente el preprocesador, el generador de código, el enlazador o cualquiera de las utilidades necesarias para la construcción de ejecutables y librerías.

Nota: en general es posible comprobar la secuencia de actuación del "compilador" invocándolo con la opción -v ("verbose"). Por ejemplo, de esta forma es posible comprobar que durante su operación, el compilador GNU cc invoca al enlazador, de nombre ld.


§2  El proceso de construcción de un ejecutable (o librería) depende de la complejidad del proyecto.  Consideramos tres casos:

  • Compilaciones unitarias:  Cuando se trata de un solo fuente que produce un ejecutable. Veremos que en estos casos puede mandarse una orden directa al programa supervisor .

  • Compilaciones medianas:  Cuando el proyecto involucra unos pocos fuentes que también producen un ejecutable. En estos casos no es corriente que el programa supervisor sea invocado directamente mediante un solo comando. Se recurre a ficheros auxiliares (de configuración), que contienen los parámetros; los ficheros de configuración son leídos e interpretados por el supervisor .

  • Proyectos grandes:  En ocasiones los proyectos software implican centenares de fuentes que producen decenas de ejecutables de diversos tipos, incluidas librerías estáticas y dinámicas. En estos casos no se puede hablar de simple compilación; más bien de "construir" la aplicación.  Mantener tales proyectos es cuestión complicada, generalmente se realiza mediante herramientas auxiliares. En el apartado correspondiente ( 1.4.0a) reseñamos una utilidad específica para controlar estas "construcciones".

Nota:  precisamente uno de los puntos fuertes de las "Suites" RAD ( 1.8) de programación del tipo C++Builder o MS Visual C++, es la cómoda gestión de proyectos grandes. Sin embargo, abrir un "proyecto" para una aplicación de un par de fuentes o un sencillo ejemplo, supone matar pulgas a cañonazos.

§3  Compilaciones unitarias

En estos casos puede mandarse una orden directa al supervisor indicándole cual es el fuente (fichero .c ó .cpp) que hay que compilar, y él se encarga de realizar todo el proceso, invocando en último extremo al enlazador que construye el ejecutable.

Por ejemplo: en el caso de Borland C++ 5.5,  Bcc32.exe está alojado en el directorio ...BorlandCPP\bin\.  Suponiendo que queremos compilar un fuente denominado hola.c con las opciones por defecto ( 1.4.3),  podemos lanzar una orden directa desde el CLI [1]:

bcc32 hola.c

Si realizamos muchas de estas compilaciones y/o utilizamos opciones, puede ser cómodo construir un fichero de proceso por lotes (.BAT en los entornos MS-DOS y Windows) que facilite la labor. A continuación se muestra el contenido de uno de estos ficheros (compi.BAT), que utilizo habitualmente para este tipo de compilaciones:

ECHO OFF
rem compi.BAT Compilar UN solo .C como C++ Normas Relajadas

bcc32 -IE:\BorlandCPP\Include -LE:\BorlandCPP\Lib -Vd -P -j5 %1%.C

rem -P Perform C++ compile regardless of source extension
rem -Vd for loop variable scoping
rem -j5 Errors: stop after n messages (Default = 25)

if errorlevel 1 goto ERROR
IF NOT ERRORLEVEL 1 ECHO !! OK. Compilacion C++ (-Vd)
GOTO END
: ERROR
ECHO !! ERROR en la compilacion....
Pause
: END

echo BORRADO fichero .TDS!!
erase %1%.TDS

Puede comprobarse que el fichero no necesita que se le indique el nombre completo del objeto a compilar (él incluye la terminación .c).  En consecuencia, solo hay que escribir:

compi hola

En este caso, los comandos -IE:\BorlandCPP\Include y -LE:\BorlandCPP\Lib, instruyen al compilador de los directorios donde debe buscar los ficheros de cabecera (comando -I) y las librerías (comando -L), que están en el directorio BorlandCPP de la unidad lógica E:

Si se realizan frecuentemente compilaciones de tipo distinto (con diversas opciones en la línea de comando), puede ser buena idea preparar los ficheros .BAT correspondientes, por ejemplo: compil.bat; compi1.bat; etc.

§4  Compilaciones medianas

Salvo en programitas de ejemplo y verificación, es muy raro que un programa C++ exista en un solo fichero fuente. En estos casos el proceso se complica un poco más, porque hay que instruir al compilador de todos los fuentes que debe procesar. Por supuesto, en uno de ellos debe existir una función main ( 4.4.4).

Ilustraremos la operativa con un sencillo programa de dos módulos pA.c y pB.c, y un fichero de cabecera específico <Cabe-1.h> que suponemos en el mismo directorio que los fuentes (no es conveniente mezclar nuestras propias cabeceras con las del compilador).

Fichero  Cabe-1.h:

// Cabe-1.h

#include <iostream.h>
#include <conio.h>
#define Salida(msg) cout << #msg << endl;
# define PAUSA for( ; ; ) if(getch()!=0) break 

Fichero  pA.c:

#include <Cabe-1.h>     // pA.c Prueba MODULOS-1

 extern void func(float);
 extern var1;

namespace {       // Anonimo
 float pi = 3.14; // identificador conocido solo en este fichero
}

int main() {      // =======
  float pi = 0.1;
  cout << "pi = " << pi << endl;
  func(pi);       // invocada función externa con pi local
  cout << "Variable es " << var1 << endl;
  Salida(Pulse una tacla para terminar);
  PAUSA;
  Salida("el programa ha terminado :-)");
  return 0;
}

Fichero  pB.c:

#include <Cabe-1.h>    // pB.c Prueba MODULOS-2

 int var1 = 33;
 namespace {           // subespacio anonimo
  float pi = 10.0001;  // conocido solo en este fichero
  void func(void) {
   std::cout << "Invocada func-1: pi = " << pi << endl;
  }
}
void func(float f) {
 std::cout << "Invocada func-2: f = " << f << endl;
}

La salida, después de construido el ejecutable es:

pi = 0.1
Invocada Segunda func(): f = 0.1
Variable es 33
Pulse una tacla para terminar
"el programa ha terminado :-)"

El comando necesario para construir el ejecutable es:

bcc32 -ID:;E:\BorlandCPP\Include -LE:\BorlandCPP\Lib -Vd -P -Q pa.C pb.c

Observe que en este caso se ha alterado ligeramente el comando -I, para que el preprocesador encuentre la cabecera <Cabe-1.h>, que está en el directorio actual (el directorio de trabajo está en la unidad lógica D:).

Para automatizar el proceso, en estos casos utilizo un fichero compiv.bat del siguiente aspecto:

ECHO OFF
rem Compiv.BAT Compilar 2 fuentes (pa.c & pb.c) como C++

bcc32 -ID:;E:\BorlandCPP\Include -LE:\BorlandCPP\Lib -Vd -P -Q pa.C pb.c
rem -Q extended compiler error information

if errorlevel 1 goto ERROR
IF NOT ERRORLEVEL 1 ECHO !! OK. Compilacion C++ (-Vd)
GOTO END
: ERROR
ECHO !! ERROR en la compilacion....
Pause
: END
echo Fichero .TDS BORRADO!!
erase %1%.TDS

§5  Ficheros de configuración

Ocurre que en ocasiones la línea de comando para invocar el supervisor es demasiado larga aún en proyectos pequeños. El motivo puede ser doble: por un lado puede contener muchas opciones de compilación y enlazado, hemos indicado que el programa Bcc32 puede contener más de 135 opciones distinta. De otro lado (y esto es lo más frecuente),  puede contener una larga serie de nombres de ficheros fuente y/o librerías que deben ser enlazadas juntas. El resultado es que incluso en un fichero de proceso por lotes .BAT como el anterior resultaría una línea larga y farragosa.

Para simplificar el proceso se utilizan ficheros auxiliares que contienen los parámetros y/o opciones de compilación. Estos ficheros son leídos e interpretados por el supervisor. Existen dos opciones al respecto [2]:  Los ficheros de configuración ("Configuration files") y los ficheros de réplica ("Computer response files").

Además de simplificar la utilización repetitiva de largos comandos de compilación, ocurre que existe una limitación en cuanto a la longitud máxima que puede tener la línea de comando en los Sistemas Operativos, y estos ficheros representan una forma de soslayar dicha limitación.

§5.1  Ficheros de configuración

Los ficheros de configuración son ficheros de texto ASCII de terminación .CFG que contienen opciones para el programa supervisor;  cada opción debe ir separada por un espacio o nueva línea.

Cuando se invoca Bcc32.exe, busca un fichero de configuración por defecto con el nombre Bcc32.CFG en el directorio actual, y si no lo encuentra, en el directorio donde reside el compilador.

El programa de instalación del compilador Borland crea un fichero Bcc32.CFG con el siguiente contenido (suponemos que se ha instalado en el directorio E:\BorlandCPP):

-I"e:\BorlandCPP\include"

-L"e:\BorlandCPP\lib"

Además del fichero por defecto, es posible utilizar varios otros en la misma línea de comando. Para invocar el programa supervisor con otro fichero de configuración, además del estándar, se utiliza la siguiente sintaxis:

+[path]nombre-de-fichero

Por ejemplo, la línea de comando que sigue invoca al supervisor con un fichero denominado Pro-1.CFG:

BCC32 +C:\PROYECTO\Pro-1.CFG fuente.cpp

  En cualquier caso, las opciones de la línea de comando pueden coexistir con un fichero de configuración, pero tienen precedencia sobre las indicaciones contenidas en aquel. Por ejemplo:

BCC32 +C:\PROYECTO\Pro-1.CFG -P fuente.c

§5.2  Ficheros de réplica

Los ficheros de réplica ("Response files") pueden contener opciones para el supervisor y/o nombres de ficheros (los de configuración solo pueden contener opciones para el supervisor). Un fichero de respuesta es también un fichero de texto ASCII donde cada entrada debe estar separada por un espacio o nueva línea. Los ficheros de respuesta pueden tener cualquier terminación (los ficheros de réplica incluidos con el compilador BC++ tienen la extensión .RSP).

§5.2.1  Contenido

El contenido es exactamente el mismo que se incluiría en la línea de comando si la invocación se hiciera manualmente y la línea pudiera ser lo suficientemente larga, pero teniendo en cuenta que en el fichero de réplica se puede partir una línea y seguir en la siguiente si se termina la primera con +.

Por ejemplo, un fichero RespFile.RSP:

/c c0ws+

myprog,myexe +

mymap +

mylib cws

pasado al enlazador con el comando:  Ilink32  @RespFile.RSP  (ver invocación ),  equivale a la línea de comando:

ILINK32 /c c0ws myprog,myexe,mymap,mylib cws

Como puede verse, a diferencia de la línea de comando, el fichero de réplica sí puede tener varias líneas. Observe que si una línea debe seguir en la siguiente pero la opción termina en el carácter +, por ejemplo la opción /v+, la línea del fichero de respuesta debe terminar en:  ...../v+ +.

Observe también que las opciones que deben ir separadas por comas en la línea de comando, deben seguir separadas por comas en el fichero de réplica (línea  myprog,myexe + del ejemplo).

§5.2.2  Invocación

La sintaxis para invocar un ficheros de réplica con el compilador es:

BCC32 @[path]fichero-replica.txt

Para invocar varios al mismo tiempo se emplea la siguiente sintaxis:

BCC32 @[path]fichero-1.txt @[path]fichero-2.txt

También en este caso las opciones de la línea de comando tienen precedencia sobre las indicadas en los ficheros de réplica.

Recuerde que no solo el compilador (BCC32) acepta este tipo de ficheros como parte del comando de entrada; el enlazador (ILINK32) y otras utilidades también puede aceptar este tipo de fichero de órdenes (por ejemplo la utilidad TLIB 1.4.0w1).

  Inicio.


[1]  CLI:  Command Line Interpreter;  intérprete de líneas de comando. El nombre concreto cambia de un SO a otro, pero se refiere a un programa que interpreta las órdenes directas del teclado. En el viejo MS-DOS es command.com.  En los sistemas Windows se suelen referir a él como el "Shell" del DOS. En los sistemas Windows 9x todavía es accesible mediante las opciones "Abrir una ventana MS-DOS" o en:  Menú de Inicio Ejecutar. Los "Linuxeros" no necesitan este tipo de aclaración, lo conocen bien; en su mundo todavía es normal funcionar a golpe de tecla.

[2]  Nuevamente nos referimos al caso de Borland C++; los demás compiladores tienen opciones análogas.