Você está na página 1de 73

Programacin C++

Adolfo J. Milln

1.4.4b2c Librera de importacin


1 Sinopsis Las libreras de importacin son libreras estticas de terminacin clsica .lib o .a, que no contienen cdigo objeto, solo tablas. Cada entrada contiene el nombre de una DLL y la direccin de un recurso exportable dentro de la DLL. Por ejemplo, una funcin dllexport de la librera. Generalmente estas direcciones estn en forma de nmeros. Nota: los compiladores C/C++ pensados para escribir aplicaciones que corrern en la plataforma Win32 incorporan algunas de estas libreras con las direcciones de los recursos de la API de Windows (que est constituida por un conjunto de DLLs). Entre las ms frecuentes de estas libreras de importacin estn: IMPORT32.LIB, KERNEL32.LIB, USER32.LIB, y GDI32.LIB.

Estas libreras se enlazan estticamente con la aplicacin que debe utilizar los recursos de la DLL, de forma que el cargador del ejecutable (que debe cargar tambin las libreras) puede resolver las invocaciones a los recursos contenidos en ellas. El juego es como sigue: supongamos tres funciones func1(); utilA() y utilB(); la primera est en la librera dinmica LibF.dll, las otras dos en LibU.dll. Supongamos que deseamos construir una aplicacin, Apli.exe, que debe utilizar dichas funciones. A su vez, los fuentes de la aplicacin estn en los ficheros Main.cpp yUtils.cpp. Una forma de proceder sera construir una librera de importacin, digamos Mi.LIB, con informacin sobre la localizacin de las tres funciones externas. Una vez construida, la librera se enlazara junto con el resto de los mdulos para conseguir un ejecutable. Por ejemplo, con el compilador de BC++ podra utilizarse el siguiente comando [1]: BCC32 -IC:\BCPP\Include -LC:\BCPP\Lib Main.CPP Utils.CPP Mi.LIB -eApli

La construccin de una librera de importacin como Mi.LIB puede hacerse de varias formas. La ms simple es crearla en el momento de crear la librera, pero tambin puede ser creada posteriormente a partir de la DLL (cuando no se dispone de los fuentes) mediante herramientas especiales. Por ejemplo, en Borland C++, mediante una utilidad denominada implib , y en GNU mediante la utilidad dlltool, que suele estar incluida junto con las "binutils" del compilador .

2 Utilidad impdef

El compilador Borland C++ dispone de la utilidad IMPDEF que permite obtener un fichero .DEF de definicin a partir de una librera .DLL; este fichero contiene una seccin EXPORTS con los nombres de todos los recursos exportables de la librera. Nota: El nombre de esta utilidad es acrnimo de "Import Definition", ya que se trata de un manejador de ficheros de definicin .DEF.

2.1 Sintaxis IMPDEF FicheroDestino.DEF NombreLibreria.DLL 2.2 Comentario Esta orden crea un fichero de definicin de nombre FicheroDestino.DEF a partir del fichero NombreLibreria.DLL. El fichero .DEF tiene el siguiente aspecto: LIBRARY NombreLibreria DESCRIPTION 'Descripcion' EXPORTS Nombre_de_funcion_exportable @Ordinal . . . Nombre_de_funcion_exportable @Ordinal Con el significado siguiente: NombreLibreria es el nombre que aparece en la variable NAME de la seccin de cabecera de la librera analizada (ver Fichero de definicin 1.4.4a). Descripcion es el valor de la seccin DESCRIPTION si la librera DLL fue enlazada utilizando un fichero de definicin .DEF que inclua una sentencia DESCRIPTION. Nombre_de_funcion_exportable Es el nombre de cada funcin exportable de la librera analizada. Ordinal es un entero que expresa el orden de dicha funcin. La utilidad acepta los siguientes comandos de ejecucin:

- Aade un guin bajo '_' al nombre de las funciones declaradas cdecl para compatibilidad a con las libreras de Microsoft. Aade algunas indicaciones extra h

2.3 Ejemplo:

Para aplicar la utilidad IMPDEF a la librera planetsB.dll construida en el ejemplo 3.2 de la pgina anterior ( 1.4.4b2a), se utilizara el siguiente comando: IMPDEF planetsB.DEF palentsB.DLL Obtenindose un fichero planetsB.DEF con el siguiente contenido: LIBRARY PLANETSB.DLL EXPORTS ___CPPdebugHook _showEarth _showMercury _showVenus @4 @3 @1 @2 ; ; ; ; ___CPPdebugHook _showEarth _showMercury _showVenus

3 Utilidad implib El compilador Borland C++ dispone de la utilidad IMPLIB que permite crear libreras de importacin a partir de una o varias DLLs y/o ficheros de definicin .DEF ( 1.4.4a). 3.1 Sintaxis IMPLIB Opciones LibName [ DefFiles... | DLLs... ] [@ResponseFile]

Opciones es una lista de uno o ms comandos opcionales, que pueden ser los siguientes:

- Aade un guin bajo '_' al nombre de las funciones declaradas cdecl ( a compatibilidad con las libreras de Microsoft.

4.4.6a) para

-c Avisos para los smbolos sensibles a maysculas/minsculas ("Case sensitive") -f Forzar importacin por nombre No indicar mensajes de aviso w

LibName es el nombre de la librera de importacin que se crear. DefFiles es una lista de uno o varios ficheros de definicin .DEF ralativos a la/s libreras utilizadas. DLLs es una lista de una o varias libreras dinmicas cuya librera de importacin se pretende crear (no tienen que ser necesariamente de terminacin .DLL, pueden ser .EXE, .DRV, etc). ResponseFile es una lista de respuesta ("Response file"). Se trata de un fichero de texto plano (ASCII) que contiene una lista de los ficheros .DEF y DLLs que se quieren procesar. Puede

ser til cuando esta lista es muy numerosa, de forma que no es necesario escribir una lnea de comando muy larga. Los nombres de ficheros de la lista deben ir separados por espacios o nueva lnea NL (ASCII 10 2.2.1a). En la lnea de comando el nombre de la lista de respuesta debe ir precedido del carcter arroba '@'. Por ejemplo: IMPLIB -w LibPrincipal.LIB @ResFiPr.txt

3.2 Ejemplo: Para obtener la librera de importacin planetsB.lib a partir de la DLL planetsB.dll construida en el ejemplo de construccin de una librera dinmica con Borland C++ 5.5 Make ( 1.4.4b2a), se utiliz el comando: implib -c planetsB.lib planetsB.dll

4 Utilidad dlltool Esta utilidad acompaa a las "binutils" del compilador GNU gcc. Es utilizada para crear los ficheros necesarios para construir y usar DLLs. La utilidad solo se incluye en los paquetes destinados a soportar DLLs (los destinados a correr en las plataformas Windows de MS). En lo que sigue, nos referimos a dlltool.exe, la versin que acompaa al compilador GNU c++ de MinGW (ver recuadro en 1.4.0a1). La utilidad dlltool puede ser utilizada de muchas formas; a continuacin incluimos un ejemplo de la ms usual: cuando tenemos una librera esttica somelib.dll de la que no son accesibles los fuentes y que se resiste a compilar con alguna de nuestras aplicaciones que la necesita (quizs por estar construida con un compilador distinto del que utilizamos -el mencionado GNU c++ de MinGW para Windows-). En estos casos la solucin puede consistir en compilar nuestra aplicacin "con" ("against" en la literatura inglesa) una librera de importacin somelib.a construida a partir de la mencionada DLL con ayuda de dlltool. Para abreviar la exposicin, utilizaremos las libreras y aplicaciones de ejemplos anteriores. En concreto, los incluidos en los captulos "Construir una DLL" ( 1.4.4b2a) y "Usar una DLL" ( 1.4.4b2b). Como resumen, recordemos que se construy una DLL con dos compiladores: GNU c++ y BC++ (Borland C++ 5.5). A continuacin se construy una aplicacin que utiliza las citadas libreras. La aplicacin se construye tambin con ambos compiladores y en sus dos versiones de enlazado para cada caso, implcito y explcito. El resumen de ficheros obtenidos es el siguiente:

Fichero

Compilador tamao Bytes Descripcin 483.328 Librera dinmica 8.192 Librera dinmica

planetsG.dll GNU gcc planetsB.dll Borland C++

planetsE.exe GNU gcc planetsD.exe GNU gcc planetsE.exe Borland C++ planetsD.exe Borland C++

16.384 Usa planetsG.dll con enlazado implcito 479.232 Usa planetsG.dll con enlazado explcito 8.192 Usa planetsB.dll con enlazado implcito 8.192 Usa planetsB.dll con enlazado explcito

Suponemos que no disponemos de la versin GNU de la librera (planetsG.dll) y necesitamos utilizar la versin BC++ disponible (planetsB.dll) para construir un ejecutable que usar enlazado explcito con la citada librera. Para ello, modificamos convenientemente el makefile makefilE.gnu( 1.4.4b2b): # MakefilE.gnu (modificado) Uso de librera Borland planetsB.dll # crear aplicacin planetsE.exe usando librera dinmica con enlazado esttico LIBS = -L"C:/DEV-CPP/lib" CXXFLAGS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \ -I"C:/DEV-CPP/include/c++/3.4.2/backward" \ -I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \ -I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include" planetsE.exe: mainE.o g++ mainE.o -o "planetsE.exe" $(LIBS) dlibs/planetsB.dll mainE.o: mainE.cpp g++ -c mainE.cpp -o mainE.o $(CXXFLAGS) La invocacin de este makefile produce los siguientes errores: mainE.o(.text+0x2b):mainE.cpp: undefined reference to `_imp__showMercury' mainE.o(.text+0x32):mainE.cpp: undefined reference to `_imp__showVenus' mainE.o(.text+0x39):mainE.cpp: undefined reference to `_imp__showEarth' lo que indica que la versin "tal cual" de la DLL no se entiende bin con nuestro compilador GNU g++. Para resolver el problema, crearemos una librera de importacin planetsB.a a partir de planetsB.dll y compilaremos con ella. El makefile a utilizar ser idntico al anterior, pero modificando adecuadamente el comando de la primera regla: planetsE.exe: mainE.o g++ mainE.o -o "planetsE.exe" $(LIBS) dlibs/planetsB.a Para construir la librera con dlltool, primero construimos un fichero de definicin planets.def relacionando en su apartado EXPORTS los identificadores que nos dieron error en la compilacin anterior: LIBRARY PLANETSB.DLL EXPORTS showMercury

showVenus showEarth A continuacin invocamos dlltool con el siguiente comando: dlltool -U -d planets.def -l planetsB.a El resultado es la librera solicitada planetsB.a, con lo que ya podemos invocarse el makefile para obtener la aplicacin planetsE.exe que funciona con la librera planetsB.dll de Borland. Nota: el tamao del ejecutable es de 20.480 bytes frente a los 16.384 del obtenido utilizando la DLL de GNU. Ver detalles adicionales en www.mingw.org En el manual de las binutils GNU puede encontrarse la informacin oficial sobre la utilidad dlltool, que acompaa a los binarios de la dististribucin MinGW.

1.4.4b2d Librera de tipos


1 Sinopsis La tecnologa de software evoluciona hacia sistemas interconectados. El tratamiento de la informacin ha ganado en complejidad, imponindose una arquitectura modular (de componentes) y transaccional; dos conceptos clave, junto con los de cliente/servidor, en la nueva arquitectura de software. Las aplicaciones se basan en componentes, objetos que interactan con otros conjuntos de objetos, del Sistema Operativo o de otras aplicaciones, locales o remotas. A este respecto, los Sistemas Windows han seguido un modelo denominado COM (Component Object Model). Un modelo de software cliente/servidor que permite la interaccin entre aplicaciones [1]. El punto importante de esta tecnologa es que permite la comunicacin entre clientes y servidores mediante interfaces normalizadas. En realidad COM naci por la necesidad de un mecanismo que permitiese que un elemento de software ofreciera servicios a otro. Esto supone que una aplicacin (cliente) pueda manipular objetos implementados en otra aplicacin distinta (servidor) y/o "exponer" objetos propios, de forma que otras aplicaciones puedan manipularlos. Observe que no es preciso que ambas aplicaciones estn escritas en el mismo lenguaje. De hecho, los objetos COM pueden escribirse en cualquier lenguaje (Java, C++, Pascal etc.) e implementarse en sus propios ejecutables o bajo la forma de libreras .DLL. A un cliente que est utilizando un objeto COM le es indiferente en que lenguaje se ha escrito o como est implementado, si se est ejecutando en una .DLL o en un proceso separado.

En la nomenclatura Windows estas manipulaciones de objetos de otras aplicaciones se denomina "automatizacin" (formalmente automatizacin OLE), y los objetos de cualquier aplicacin (objeto servidor) puede ser manipulada siempre que exponga una interfaz adecuada (propiedades y mtodos) que puedan ser accedidas desde otras aplicaciones (los clientes).

Los clientes "automatizados" pueden ser locales o remotos (incluso en una mquina accesible en una red). Muchas aplicaciones comerciales, incluyendo algunas del propio Microsoft como Excel o Word disponen de esta interfaz, de forma que gran parte de su funcionalidad puede ser automatizada (monitorizada) desde otras aplicaciones. Los clientes deben disponer de informacin sobre la interfaz del objeto servidor para poder manipularlo. Esta informacin se concreta en conocer el tipo de datos de las propiedades, as como valores devueltos y argumentos utilizados por los mtodos. Si queremos que un cliente tenga estas habilidades, toda esta informacin (del servidor) debe ser enlazada con l durante su construccin. Esto puede hacerse de varias formas, pero la ms adecuada es crear una librera de tipos. Las libreras de tipos, son un tipo especial de librera de importacin (esttica), que se enlaza con el cliente y le proporciona informacin sobre la forma de acceder a la interfaz del servidor (en realidad informan sobre la forma pasar punteros de modo local o remoto -a travs de redes- a losservidores). Nota: se utiliza el trmino librera de tipos cuando el objeto descrito en la librera no es una librera dinmica normal, sino un control ActiveX, un servidor OLE, un servidor COM.

1.4.4b2e Carga retrasada


1 Sinopsis La carga retrasada ("delay load") es una opcin de compilacin utilizable en la compilacin de ejecutables que deban utilizar recursos situados en libreras dinmicas. Resulta de utilidad con libreras que se usan raramente en un programa pero que son muy voluminosas, y por tanto, tienen un gran coste de carga. Las DLLs de carga retrasada no son cargadas e inicializadas hasta que el programa alcanza un punto en que se intenta acceder a la librera. De lo anterior se deduce que se trata de un caso particular de enlazado explcito (dinmico) con el ejecutable que las utiliza. Ejemplo [2]: ilink32 /dMyDataBase.dll c0x32 x ,x,,import32 cw32mt MyDataBase.lib

En este caso, la opcin /d hace que la librera MyDataBase solo sea cargada cuando realmente se utilice alguno de sus recursos. Nota: La RTL del compilador BC++ dispone de utilidades que permiten al usuario cargar a voluntad estas libreras para depurar errores de carga, e incluso suplantar el sistema de retraso si se desea.

2 Empleo La opcin de carga retrasada puede ser una opcin a considerar si:

Hay posibilidad de que un recurso no llegue a ser utilizado por el programa. Por ejemplo, solo se utiliza en determinadas condiciones que pueden no llegar a ocurrir. Un recurso cuya carga es costosa (en trmino de tiempo e inicializacin) solo se requiere hasta un tiempo despus de iniciado el programa. De esta forma, al costo de inicio del programa no se le suma el de la DLL. La impresin del usuario es que el programa se inicia ms rpidamente.

3 Restricciones El compilador BC++ no permite usar el dispositivo de carga retrasada en los siguientes casos: Una librera que contenga referencias a otros datos que deban ser tambin importados. La librera KERNEL32.DLL (una librera de la API de Windows). La librera RTLDLL, ya que esta librera contiene precisamente las rutinas que soportan la carga retrasada.

4 Descargar una librera de carga retrasada. Despues que una DLL ha sido cargada con retraso, puede ser conveniente descargarla manualmente. Para ello, tanto BC++ como MSVC disponen de una funcin especfica de librera, __FUnloadDelayLoadedDLL, que puede descargar la librera y resetear el mecanismo de carga por si fuese necesaria de nuevo en el futuro. 4.1 Sintaxis #include <delayimp.h> BOOL WINAPI __FUnloadDelayLoadedDLL(LPCSTR szDll);

szDll es un puntero al nombre de la librera a descargar o un valor nulo ( NULL) para descargar todas las libreras de carga retrasada. 4.2 Descripcin Como puede verse, esta funcin devuelve un bool ( 3.2.1b). En caso de xito el valor devuelto es cierto (true) en caso contrario devuelve falso (false). 4.3 Comentario Deben observarse precauciones especiales al descargar libreras de carga retrasada en una aplicacin multihebra ( 1.7). Pueden presentarse problemas si una de las tareas obtiene la direccin de una funcin de librera al mismo tiempo que otra tarea procede a la descarga de la DLL correspondiente.

1.4.5 Depuracin
"Software is a conversation, between the software developer and the user. But for that conversation to happen requires a lot of work beyond the software development. It takes marketing, yes, but also sales, and public relations, and an office, and a network, and infrastructure, and air conditioning in the office, and customer service, and accounting, and a bunch of other support tasks". Joel Spolsky. "The Development Abstraction Layer". www.joelonsoftware.com.

1 Generalidades La depuracin est estrechamente relacionada con el concepto de calidad. En un sentido amplio, depurar un programa significa librarlo de errores e inconvenientes ms o menos graves, que con frecuencia es un proceso mucho mas costoso y arduo de lo que pudiera parecer a primera vista, en especial en programas grandes y complejos. Sin embargo, es imprescindible si queremos ofrecer al pblico un producto con un mnimo de calidad. Es interesante comprobar como la posicin comercial de las grandes empresas de software ha evolucionado hasta una posicin que podra considerarse envidiable y casi nica, en relacin con los productos producidos por el resto de la industria. Los que manejamos habitualmente software de cualquier tipo, desde la ms humilde utilidad shareware [1] hasta los propios sistemas operativos, estamos acostumbrados a ver durante el proceso de instalacin clusulas en las que se anuncia que el producto se vende "Tal cual" ("As is"); sin ningn tipo de garanta o de responsabilidad que pudiera derivarse de su posible mal funcionamiento. Es cierto que el software tiene unas caractersticas muy especiales y que se podra decir mucho al respecto, pero no es menos cierto que habitualmente aceptamos condiciones que no le permitiramos al fabricante que nos vende un automvil, un par de zapatos o una lata de conserva por ejemplo. Podemos observar como incluso productos software muy conocidos y de gran consumo, producidos por compaas muy poderosas [4], son lanzados a veces sin un proceso de depuracin adecuado; motivado la mayora de las veces por la excesiva presin de su propia maquinaria de mrketing, que las fuerza a lanzar nuevos productos y versiones a un ritmo desenfrenado. En Junio de 2002, el NIST ("National Institute of Standards and Technology"), organismo dependiente del Gobierno USA publicaba un informe titulado "The Economic Impacts of Inadequate Infrastructure for Software Testing", en el que se analiza la repercusin econmica debida a los defectos del software en dos sectores econmicos del pas: Servicios financieros, e industria de fabricacin de medios de transporte. El resultado es que solo en estos sectores, el coste de los errores (bugs) derivados de la incorrecta depuracin del software asciende a unos 60.000 millones de dlares anuales [5].

2 Depuracin

En general la depuracin de un programa se completa en tres fases; es un proceso en el que el producto se va acercando a la perfeccin (estar libre de errores e inconvenientes graves) mediante una serie de transformaciones sucesivas que responden al esquema de la figura.

En una primera fase, cuando ya el programa est prcticamente terminado, se somete a pruebas que podramos llamar "de laboratorio" para comprobar que todo marcha segn lo esperado y sin errores [2]. Son pruebas que realiza el propio programador antes de dar a conocer el producto. Estas versiones se suelen denominar alfa y corresponden a un punto en que el programa todava est en fase de gestacin. En una versin alfa son de esperar todo tipo de errores y "cuelgues". En una segunda fase, cuando el programador cree que su producto ya est suficientemente presentable y l no consigue encontrar ms errores aparentes (o los ya conocidos estn en proceso de depuracin), se procede a la distribucin del producto a una serie de probadores seleccionados ("beta testers"). Son las denominadas versiones beta, que aunque con errores espordicos, pueden tener un comportamiento ms o menos aceptable. Finalmente, en una tercera fase, con la informacin, opiniones y sugerencias de los "beta testers", se procede a lanzar la primera versin pblica v 1.0 del producto ("Release"), tambin denominadas versiones gamma. A partir de aqu, lo normal es que se vayan recogiendo las sugerencias, cuestiones y posibles errores que hayan pasado inadvertidos en las pruebas beta y sean reportados por los usuarios. Las soluciones a estos problemas, junto con las mejoras que se vayan incorporando, son implementados en versiones sucesivas. Generalmente a partir de la versin 2.0 el producto se considera estable.

3 El cuaderno de bitcora Es frecuente que los programadores cuidadosos mantengan una especie de registro histrico o cuaderno de bitcora donde se recoge la evolucin del programa. Puede tratarse de un simple fichero de texto plano con cualquier nombre alusivo: Cambios.txt; Historia.txt; Versiones.txt [6], Etc. En l se recoge la informacin que se estima pertinente. Por ejemplo, las fechas de publicacin de las diversas versiones; las mejoras introducidas respecto a las anteriores, y sobre todo los errores ("bugs") corregidos. Este fichero suele acompaar a cada nueva versin, pero lo mejor es darles la mxima publicidad entre los usuarios potenciales y actuales. Los primeros, porque se pueden hacer una idea de la vitalidad del producto (una historia larga de actualizaciones regulares y frecuentes es buena seal). A los usuarios actuales porque les permite evaluar si les merece la pena o no el proceso de actualizacin a las nuevas versiones. En lo que respecta a este captulo, nos referiremos exclusivamente a la depuracin de versiones alfa, en las que los procesos de construccin del ejecutable suelen incluir los mecanismos de depuracin, que posteriormente se omiten en las versiones de campo. Nota: Llegados a este punto, aconsejamos al lector novel saltarse por ahora el resto del captulo y seguir con las siguientes secciones. Para lo que sigue es necesario conocer

previamente los captulos dedicados a: Tratamiento de Excepciones ( Operadores de Preproceso ( 4.9.10).

1.6) y los

4 El depurador Como se ha sealado anteriormente ( 1.4), los compiladores tienen una opcin que permite incluir o no informacin adicional de depuracin en el ejecutable. Esta informacin adicional consiste bsicamente en la inclusin del nmero de lnea (del cdigo fuente) de cada sentencia, aunque tambin se pueden tomar otras medidas. Por ejemplo, tratar todas las funciones como si fuesen normales, no realizndose en estos casos sustituciones inline ( 4.4.6b). Nota: en el caso del compilador Borland C++ 5.5, la opcin correspondiente es el comando v, que est conectado (ON) por defecto ( 4.4.6b). El compilador GNU gcc utiliza la opcin g para este propsito. No olvide que la presencia de la informacin de depuracin produce un gran aumento de tamao del ejecutable, por lo que solo debe utilizarse cuando verdaderamente sea necesaria.

Los entornos de desarrollo C++ actuales incluyen potentes depuradores con los que es posible controlar prcticamente todos los aspectos de ejecucin de versiones alfa. Su manejo depende naturalmente de cada caso concreto, pero en general permiten inspeccionar el estado de llamadas de la pila, lo que significa controlar la "traza" de la ejecucin. De esta forma es posible conocer el camino que ha seguido la ejecucin hasta llegar a un punto concreto (que funciones han sido invocadas y cual es el valor de las variables). Por ejemplo, cuantas veces se ha invocado a s misma una funcin recursiva y que valores tienen sus variables en cada una de las instancias; el valor de las variables globales, locales, automticas y estticas. Las opciones anteriores son las que podramos llamar mnimas. Por supuesto las versiones ms avanzadas de los productos punteros permiten hurgar ms cmoda y profundamente en las entraas del ejecutable. Por ejemplo, depurar funciones miembro y aplicaciones multihebra; controlar la ejecucin paso a paso; a nivel de instrucciones mquina (ensamblador) o a nivel de sentencia de nuestro cdigo fuente, as como instalar puntos de control ("break points") en el ejecutable. Se trata de instrucciones especficas instaladas a la entrada de las zonas de cdigo donde sospechamos que se presentan los problemas. Esto nos permite correr el programa a velocidad normal, pero al llegar a dichos puntos, es invocado automticamente el depurador que nos muestra todas sus herramientas [3]; entonces se pueden realizar las comprobaciones oportunas, y si todo est correcto, volver a modo ejecucin normal hasta que se alcanza el prximo punto de control (en los ejemplos que siguen aprenderemos a instalar en nuestro cdigo puntos de control rudimentarios sin necesidad de utilizar ningn depurador). Para cuando estas posibilidades no bastan, o sencillamente no se dispone de ellas, existen una serie de trucos y tcnicas generales que facilitan la depuracin. El proceso ms general y socorrido consiste en incluir en el cdigo una serie de puntos testigo ("flags") con salidas provisionales que nos informan que el programa ha pasado por el punto sin novedad y los valores de las variables sospechosas de mal funcionamiento.

Ejemplo 1: Una forma sencilla de implementar estos semforos puede ser utilizar un define ( Como ejemplo construimos un programa que muestre el paso por diversos puntos:

4.9.10b).

#include <iostream.h> #define testigo(punto) cout << "Alcanzado punto " #punto << endl; int main() { char* ptr = int x = 0; testigo(1); cout << ptr testigo(2); cout << ptr testigo(3); cout << ptr testigo(4); } Salida: Alcanzado punto El valor actual Alcanzado punto El valor actual Alcanzado punto El valor actual Alcanzado punto 1 de X: 0 2 de X: 1 3 de X: 2 4 // ============== "El valor actual de X: "; << x++ << endl; << x++ << endl; << x++ << endl;

Ejemplo-2 Es posible que nos interese detener el proceso en cada punto con objeto de ver con comodidad la evolucin. En este caso, unas pequeas modificaciones en el las directivas del ejemplo cumplen perfectamente el cometido: #include <iostream> #include <conio.h> // para getch() #define PAUSA for( ; ; ) if(getch()!=0) break ; #define testigo(punto) cout << "Alcanzado punto " #punto << endl; PAUSA using namespace std; int main() { char* ptr = int x = 0; testigo(1); cout << ptr testigo(2); cout << ptr testigo(3); cout << ptr testigo(4); } // ============ "El valor actual de X: "; << x++ << endl; << x++ << endl; << x++ << endl;

En este caso, el cuerpo de main y la salida son idnticas al anterior, pero despus de informar del paso por cada punto, el programa espera la pulsacin de una tecla antes de continuar.

Ejemplo 3 Si el programa es complejo puede interesar no tener que estar pendientes de la situacin de los semforos que vamos colocando ni de su numeracin. Una opcin puede ser utilizar las constantes simblicas ( 1.4.1a) del compilador. En la nueva versin que presentamos, el testigo nos informa automticamente del fichero y nmero de linea del cdigo fuente a que corresponde el punto que se est ejecutando. #include <iostream.h> #include <conio.h> // para getch() #define PAUSA for( ; ; ) if(getch()!=0) break ; #define testigo cout << "Alcanzada linea " << __LINE__ << " modulo: " << __FILE__ << endl; PAUSA int main() { char* ptr = int x = 0; testigo; cout << ptr testigo; cout << ptr testigo; cout << ptr testigo; } // ================ "El valor actual de X: "; << x++ << endl; << x++ << endl; << x++ << endl;

Salida (el fichero fuente utilizado se denomina p3.c ): Alcanzada linea El valor actual Alcanzada linea El valor actual Alcanzada linea El valor actual Alcanzada linea 9 modulo: p3.c de X: 0 11 modulo: p3.c de X: 1 13 modulo: p3.c de X: 2 15 modulo: p3.c

Ejemplo 4 En ocasiones puede resultar molesto tener que repasar la totalidad del cdigo para eliminar las sentencias testigo provisionales de depuracin. Este trabajo extra puede eliminarse fcilmente utilizando una directiva adecuada que ponemos y quitamos alternativamente segn que la versin a construir sea o no de depuracin: #include <iostream.h> #include <conio.h> #define PAUSA for( ; ; ) if(getch()!=0) break ; #define DEBUG // L.4: Comentar para versiones definitivas #ifdef DEBUG #define testigo cout << "Alcanzada linea " << __LINE__ << " modulo: " << __FILE__ << endl; PAUSA #else #define testigo #endif int main() { // ===============

char* ptr = int x = 0; testigo; cout << ptr testigo; cout << ptr testigo; cout << ptr testigo; }

"El valor actual de X: "; << x++ << endl; << x++ << endl; << x++ << endl;

La salida es anloga a la del ejemplo anterior, pero con esta disposicin de las directivas de cabecera, solo hay que comentar L.4 para pasar a la versin definitiva; automticamente quedarn eliminadas del cdigo las sentencias de depuracin. En la prctica, este tipo de disposiciones se realizan en un fichero de cabecera particular que almacenamos aparte y que incluimos en todos los mdulos que componen nuestro programa. En nuestro caso, podramos definir un fichero MiCabecera.h con el siguiente contenido: #include <iostream.h> #include <conio.h> #define PAUSA for( ; ; ) if(getch()!=0) break ; #define DEBUG // L.4: Comentar para versiones definitivas #ifdef DEBUG #define testigo cout << "Alcanzada linea " << __LINE__ << " modulo: " << __FILE__ << endl; PAUSA #else #define testigo #endif Posteriormente, solo necesitamos incluir una directiva de preproceso en el fichero fuente p3.c (y en cualquier otro que incluyera nuestra aplicacin). Por ejemplo: #include <MiCabecera.h> int main() { // =============== char* ptr = "El valor actual de X: "; int x = 0; testigo; cout << ptr << x++ << endl; testigo; cout << ptr << x++ << endl; testigo; cout << ptr << x++ << endl; testigo; }

5 La funcin assert() La librera Estndar C++ contiene una funcin particular que entre otros usos, puede servir muy bien para ayudar en la depuracin. Se trata de la funcin assert, cuya definicin es: void assert(int test);

La funcin no devuelve nada y espera un entero como argumento. En realidad se trata de una macro ( 4.9.10b) contenida en el fichero de cabecera <assert.h> que sustituye la funcin por un trozo de cdigo del siguiente aspecto: #ifdef NDEBUG #define assert(p) ((void)0) #else #define assert(p) ((p) ? (void)0 : _assert(#p, __FILE__, __LINE__)) #endif Como puede comprobarse, el proceso de compilacin sustituye la funcin por una expresin condicional ( 4.9.6) siempre que no est definida la etiqueta NDEBUG (sin depuracin); en caso contrario, la funcin es sencillamente eliminada del cdigo (ms propiamente podramos decir que es sustituida directamente por el valor que devuelve: void 2.2.1 sin que se realice ninguna accin). La funcin acepta como argumento cualquier valor que pueda ser promovido a entero (incluyendo punteros y expresiones relacionales). Posteriormente, el entero es considerado el resultado de una expresin relacional ( 4.9.12); si el valor es cierto (un valor distinto de cero), el programa sigue su ejecucin normal, pero si es falso (valor igual a cero), se invoca la funcin _assert con tres parmetros: una cadena alfanumrica, copia del argumento test utilizado, y las constantes simblicas (

1.4.1a) __FILE__ y __LINE__.

Lo que hace a su vez la funcin interna _assert, depende del tipo de aplicacin; de que se haya compilado, o no, con las opciones de depuracin y de la plataforma. En las aplicaciones de consola (no grficas) se lanza un mensaje por el dispositivo estndar de salida de errores stderr ( 5.3): Assertion failed: test, file nombre-de-fichero, line numero-delnea; a continuacin se invoca la funcin abort (

1.5.1) que termina la ejecucin.

Como ejemplo vamos a construir un programa que construye una tabla de inversos de nmeros enteros, en el que incluimos una comprobacin de que en ningn caso el valor de la variable x sea igual a cero, ya que este valor supondra un error numrico en el programa (divisin por cero): #include <iostream.h> #include <assert.h> #define NDEBUG void func(int i); // prototipo

int main() { // =============== for (int x = 5; x <= 5; x--) { assert(x != 0); func(x); } } void func(int i) { // definicin float f1 = float(1)/float(i); cout << "X = " << i << " 1/x = " << f1 << endl; }

Salida: X X X X X = = = = = 5 4 3 2 1 1/x 1/x 1/x 1/x 1/x = = = = = 0.2 0.25 0.333333 0.5 1

Assertion failed: x != 0, file p3.c, line 9 Abnormal program termination

Como se ha visto, cada aparicin de la funcin assert en el cdigo supone la verificacin de una hiptesis. Durante la depuracin de las versiones alfa, es buena prctica la colocacin sistemtica de comprobadores de este tipo en todos los puntos que sea posible, a fin de garantizar que las condiciones de contorno se mantienen dentro de lo esperado.

1.4.6 Tamao de los ejecutables


1 Justificacin Aunque importante, la cuestin del tamao de los ejecutables quizs no debera merecer atencin especial en un curso bsico de programacin C++ como el presente. Sin embargo, el asunto suele ser motivo de desconcierto entre los que se aproximan por primera vez a la programacin C++. Por ejemplo, en los foros son frecuentes las controversias sobre los tamaos obtenidos en las compilaciones C y C++. En consecuencia, hemos decidido incluir aqu unas breves consideraciones sobre el tema, con la esperanza de que ayuden al lector a contemplar el asunto en sus justos trminos y a identificar los factores que influyen en l. 2 Sinopsis Deberamos comenzar diciendo que "el tamao del ejecutable" es una expresin un tanto ambigua. Recordemos que una cosa es el fichero .exe(conocido tambin como fichero-imagen), tal como aparece en un medio de almacenamiento externo (disco), y otra su disposicin y por consiguiente su tamao, cuando se ha cargado en memoria. En principio ambos tamaos no tienen ninguna relacin entre s, de forma que a un fichero .exe pequeo puede corresponderle un tamao en memoria mucho mayor. Por ejemplo, si el fuente define una matriz cuyo espacio se reserva en runtime. Pensemos tambin que la pila puede crecer casi indefinidamente en el caso de la invocacin de funciones con gran nivel de anidamiento ( 4.4.6b) como es el caso de las funciones recursivas ( 4.4.6c). Incluso para ser ms exactos, debemos recordar que la disposicin en memoria del ejecutable no se refiere necesariamente a un espacio nico, ya que generalmente ocupa distintas reas que no son necesariamente contiguas ( 1.3.2). Por consiguiente, ms que de tamao del ejecutable en memoria, deberamos referirnos al de sus distintos mdulos.

2.1 Ejecutable en memoria En tiempos, la memoria disponible para el ejecutable era una cuestin primordial. En MS-DOS vena limitada por la fatdica barrera de los 640 KB ( H5.1). Sin embargo, la memoria dinmica en los sistemas actuales ha reducido su importancia, solo reseable quizs en sistemas embebidos (microporcesadores dedicados) donde la memoria suele ser limitada. En lo que se refiere al montn y a la pila, recordemos que los compiladores actuales disponen de opciones para limitar el tamao de memoria que utilizar el ejecutable ( 1.4.4a Heapsize, Stacksize). En lo referente a los tamaos del segmento de datos y del cdigo ("data segment" y "code segment"), son de aplicacin algunas de las observaciones de carcter general que se relacionan a continuacin. 2.2 Ejecutable externo En lo que respecta al tamao del ejecutable en su forma externa ( .exe), que es el aspecto ms aparente, y por consiguiente el ms generalmente utilizado, son pertinentes algunas observaciones de carcter general: El tamao del fichero .exe solo tiene importancia en el momento inicial de la carga; cuando es trado a memoria desde el disco, o a travs de la red. Si exceptuamos casos extremos, como las aplicaciones embebidas ya sealadas, el espacio de almacenamiento del fichero .exe, con ser importante, no lo es tanto como en pocas pasadas, cuando las aplicaciones deban ser distribuidas en disquetes. Actualmente la distribucin se realiza mayoritariamente por la red en sus distintas formas, o mediante CDs y DVDs, cuyas capacidades exceden con mucho la que necesitan las aplicaciones ms exigentes. Nota: la distribucin de MS-DOS 6.2 de 1993, ocupaba 4 disquetes de alta densidad HD (1.44 MB). La distribucin de Microsoft Windows para Trabajo en Grupo del mismo ao, ocupaba 9 disquetes HD. En la actualidad (2006) las distribuciones de Windows ocupan varios cientos de MB. Recordar que existen utilidades ("packers") capaces de reducir ("squeeze") el tamao del ejecutable, lo que puede ser til para su almacenamiento y para su transporte por redes. Sin embargo, estos ficheros deben ser descomprimidos en memoria para su carga, momento en que desaparecen las bondades del sistema [1].

3 Factores que influyen en el tamao del ejecutable Recordemos que, al igual que ocurre con la velocidad de ejecucin, en lo relativo al tamao no existen medidas mgicas o milagrosas. Es ms bien una cuestin de paciencia y de mtodo (ensayar muchas alternativas que comienzan con el diseo del cdigo). A continuacin reseamos algunos factores que influyen en el tamao del ejecutable, pero debemos advertir que su influencia depende de las circunstancias particulares; unas veces puede ser mnima, otras decisiva y espectacular. En cualquier caso, es incumbencia de desarrollador conocerlos y decidir la combinacin de medidas ms adecuada a sus circunstancias. Los compiladores modernos utilizan distintos criterios de optimizacin. Generalmente permiten seleccionar que criterio ser dominante: la velocidad de ejecucin o el tamao del ejecutable ( 1.2).

La utilizacin de macros en lugar de funciones ( 5.1), mejora la velocidad de ejecucin en detrimento del tamao del ejecutable. La utilizacin del mecanismo de identificacin de tipos en tiempo de ejecucin (RTTI 4.9.14) obliga a la inclusin de determinadas libreras que aumentan el tamao del ejecutable. La utilizacin del mecanismo de excepciones ( 1.6) obliga al compilador a incluir cdigo extra necesario para lanzar y propagar la excepcin. En algunos casos, esto obliga a generar la informacin necesaria para descargar el marco de pila ("frame unwind") para todas las funciones, lo que se traduce en un aumento significativo del tamao del cdigo. Recordar que en los compiladores C++, este mecanismo suele estar habilitado por defecto, aunque puede ser deshabilitado. La utilizacin de determinados recursos de la Librera Estndar de Plantillas STL ( 5.1) puede originar un aumento considerable del tamao del ejecutable resultante ("code bloat"). En general la utilizacin juiciosa de la STL exige cierta prctica. Como sealbamos en el captulo anterior ( 1.4.5), la informacin de depuracin es sin duda la mayor responsable del aumento del tamao de los ejecutables. En consecuencia, no olvide que esta informacin debe ser suprimida en las versiones definitivas de los ejecutables, o en las que no requieran depuracin. Lo ms prctico es disponer de makefiles ( 1.4.0a) distintos para las versiones de desarrollo y para las de campo. Los ejecutables pueden incluir una tabla de smbolos. Esta informacin es incluida por el enlazador para ayudar en la depuracin y para distintas utilidades. En ocasiones, una parte de esta informacin es imprescindible. Por ejemplo la que relaciona smbolos situados en el exterior (importables). Pero es frecuente, incluso cuando no se ha solicitado expresamente informacin sobre depuracin, que el enlazador incluya smbolos no estrictamente necesarios para la ejecucin (la mayora ha desaparecido durante el enlazado). Esta tabla, que en ocasiones es enorme y responsable de buena parte del tamao del ejecutable, puede ser reducida mediante ciertas opciones de compilacin que "desnudan" el cdigo durante el proceso de enlazado [3], y mediante utilidades como strip, que eliminan todos los smbolos que no son estrictamente necesarios para la ejecucin sobre un ejecutable ya construido. Recuerde que las libreras estticas incrementan el tamao del ejecutable, mientras que las dinmicas permiten concentrar parte de la funcionalidad requerida en ficheros independientes (.dll), y que estos pueden ser compartidos por varios ejecutables ( 1.4.4b).

Nota: Como ejemplo de lo anterior, incluimos algunos comentarios (respetando su redaccin original en ingls) relativas al efecto de la inclusin de la Librera Estndar en el tamao de los ejecutables: Why is my C++ binary so large? C++ programs using the Standard Template Library (ie/ #include <iostream>) cause a large part of the library to be statically linked into the binary. The need to statically link the stdc++ into the binary is two fold. First MSVCRT.dll does not contain C++ stdlib constructs. Second the legal implications of generating a libstdc++.dll are restricted by the licensing associated with the library. If you wish to keep your file size down use strip to remove debugging information and other verbatim found in the binary. strip --strip-all SOMEBINARY.exe Tomado de: MinGW - Frequently Asked Questions www.mingw.org Why is the compiled executable file so large?

People usually ask this question when they compile a simple program which uses iostreams. The first thing you can do is to add -s to Project Options - Parameters - Linker, but the result may be still too large for your taste. In this case, either try to live with it (it actually doesn't matter so much!), or avoid iostreams (use cstdio), or use another compiler. Also note that there are some exe compressors on the net, e.g. upx. The reason why iostream increases the size so much is that the linker links entire object files (from inside of libraries) if they contain at least one necessary reference, and the library for iostream is not well separated into small object files. Also, the linker should be able to link only certain sections of the object files (see "--gc-sections"), but this particular feature doesn't work yet on the mingw target (and that affects all libraries and object files). Tomado de: Adrian Sandor www14.brinkster.com

4 Ejemplo Como muestra utilizaremos una aplicacin C++ de consola, con el consabido "Hola mundo", en dos versiones: la primera p1.cpp, utiliza los recursos de la Librera Estndar C++; la segunda p2.cpp, utiliza la librera clsica para producir la salida. Construimos versiones del ejecutable con el compilador Borland C++ 5.5 y con la versin c++ de GNU para Windows de MinGW. // p1.cpp Librera Estndar C++ #include <iostream> int main() { std::cout << "Hola mundo" << std::endl; system ("PAUSE"); return EXIT_SUCCESS } // p2.cpp Librera Clsica #include <stdio.h> #include <stdlib.h> int main() { printf("Hola mundo\n"); system ("PAUSE"); return EXIT_SUCCESS; } Para cada versin del fuente se realizan dos compilaciones; la primera con las opciones por defecto; la segunda con las opciones adecuadas para reducir al mximo el tamao del ejecutable. La tabla adjunta muestra los resultados obtenidos en cada caso.

Fuente Compilador Makefile Bdefault p1.cpp Borland C++ Bopt

Tamao [2] 117.248 117.248

GNUdefault GNU c++ GNUopt Bdefault Borland C++ Bopt p2.cpp GNUdefault GNU c++ GNUopt

474.953 266.240 56.320 56.320 15.839 4.096

Puede comprobarse que en ambos casos, el compilador Borland se muestra muy eficiente respecto a la optimizacin alcanzada con las opciones por defecto. No obstante, los ejecutables p1.exe y p2.exe obtenidos, de 117.248 y 56320 Bytes, pueden ser reducidos hasta 116.736 y 55.808 Bytes respectivamente mediante la utilidad strip [4]. Puede observarse tambin que las Libreras Estndar de la versin GNU para Windows parecen poco optimizadas en cuanto al tamao; el mejor resultado es casi el doble que el conseguido con Borland. Sin embargo, la ventaja de GNU para las libreras clsicas es aplastante respecto a los resultados de Borland. Observe finalmente que la diferencia entre los tamaos extremos conseguidos, 4.000 frente a casi 475.000 Bytes, muestran claramente que en esta cuestin, las diferencias pueden ser muy abultadas en funcin del compilador y de las circunstancias. Los mekefiles utilizados son los siguientes: # Bdefault Makefile para Borland; opciones por defecto LIBS = -LE:\BorlandCPP\Lib INCS = -IE:\BorlandCPP\Include all: p1.exe p1.exe: bcc32 $(INCS) $(LIBS) p1.Cpp # Bopt Makefile para Borland optimizado LIBS = -LE:\BorlandCPP\Lib INCS = -IE:\BorlandCPP\Include all: p1.exe p1.exe: bcc32 $(INCS) $(LIBS) -v- -O1 -RT- -x- -xd- p1.Cpp # GNUdefault Makefile para GNU; opciones por defecto LIBS = -L"C:/DEV-CPP/lib" CXXFLAGS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \ -I"C:/DEV-CPP/include/c++/3.4.2/backward" \ -I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \ -I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include" all: p1.exe

p1.exe: p1.o g++ p1.o -o "p1.exe" $(LIBS) # GNUopt Makefile para GNU optimizado LIBS = -L"C:/DEV-CPP/lib" # -lcomctl32 CXXFLAGS = -I"C:/DEV-CPP/lib/gcc/mingw32/3.4.2/include" \ -I"C:/DEV-CPP/include/c++/3.4.2/backward" \ -I"C:/DEV-CPP/include/c++/3.4.2/mingw32" \ -I"C:/DEV-CPP/include/c++/3.4.2" -I"C:/DEV-CPP/include" all: p1.exe p1.exe: p1.o g++ p1.o -o "p1.exe" -s -Os -fno-rtti -fno-exceptions $(LIBS)

1.5 Secuencia de ejecucin de un programa


Desde que es invocado por su entorno (normalmente el Sistema Operativo) hasta que termina, la ejecucin de un programa C++ recorre las siguientes etapas:

1 Se ejecuta el mdulo inicial. Durante la fase de enlazado de la compilacin, el enlazador aade a cualquier programa C++ un mdulo especial, de inicio, que es realmente el punto de entrada a la ejecucin del programa [ 2]. Este mdulo realiza diversas tareas, entre ellas iniciar todas las variables estticas o globales (ver nota), controlar la ejecucin de posibles funciones opcionales de inicio (ver punto siguiente) y finalmente, invocar la funcin main ( 4.4.4). Nota: cuando se compila cualquier mdulo, en el objeto resultante existe un segmento denominado _INIT_ que contiene una referencia ("Init entry") al constructor de cada objeto global que deba ser inicializado, as como un orden de prioridad. Ms tarde, cuando el enlazador construye un ejecutable, estas entradas se agrupan en una tabla, la tabla de inicio ("Init table") que contiene ordenadamente todas las entradas de los constructores de los objetos que existen en los mdulos que componen el programa. Finalmente, esta tabla es utilizada por el mdulo de inicio cuando el programa es iniciado.

2 Se ejecutan las funciones de inicio. Algunos compiladores permiten al programador la opcin de que se invoquen determinadas funciones, antes que se realice la llamada a main. Estas funciones representan tareas preparatorias adicionales, que queremos realizar antes de que se inicie la ejecucin. Esto se consigue con la directiva de inicio #pragma startup ( 4.9.10i).

3 Se invoca la funcin main() Despus de lo anterior, el control pasa a una funcin que debe responder al nombre de main y le pasa algunos argumentos en base a datos que ha recibido a su vez del Sistema Operativo (

4.4.4). As pues, en ausencia de funciones particulares de inicio, main representa el punto de la ejecucin a partir del cual el programador toma control de la ejecucin. El cuerpo de esta funcin representa la ejecucin del programa, su "carga til".
4 Se ejecutan las funciones de salida. Algunos compiladores permiten la opcin de llamar determinadas funciones antes que se alcance el final del programa, justo antes de su terminacin. Estas funciones, que representan tareas adicionales que queremos realizar antes de que el programa devuelva el control a su entorno de ejecucin. Se establecen con la directiva #pragma exit ( 4.9.10i). La Librera Estndar C++ tambin proporciona una funcin especial: atexit() que permite la opcin de ejecutar un cdigo especfico en la terminacin ( 1.5.1).

5 Finalizacin Un programa C++ puede terminar por varios motivos: Se termine la funcin main(). Bien porque se alcance el corchete de cierre "}", bien porque se encuentra una sentencia return; esto ltimo es lo deseable (aunque no imprescindible), ya que el estndar define que main debe devolver un int a su entorno de ejecucin ( 4.4.4). Se encuentra una invocacin a la funcin exit(), una funcin de la Librera Estndar ( 1.5.1). Se encuentra una invocacin a la funcin abort(), otra funcin Librera Estndar ( 1.5.1). Se lanza una excepcin que no encuentra su manejador ("handler") correspondiente ( 1.6.3 Excepciones imprevistas).

En el apartado 1.5.1 se incluye la descripcin de algunas de las funciones relativas a la terminacin de un programa [1]. Hay que recordar que en un programa C++ todos los objetos globales se mantienen activos hasta que se han ejecutado todas las funciones de salida. Las variables locales, incluyendo las declaradas en la funcin main son destruidas a medida que quedan fuera de mbito. Al final de un programa C++ Builder el orden de ejecucin es como sigue: Las funciones de salida son ejecutadas en el mismo orden que fueron insertadas por atexit. Las funciones declaradas en #pragma exit son ejecutadas en orden a sus cdigos de prioridad. Se llama al destructor de variables globales.

Ver algunos comentarios adicionales sobre un caso concreto:

2.2.6.

1.5.1 Funciones de terminacin


1 Sinopsis C++ dispone en su Librera Estndar de algunas funciones especficamente relacionadas con la terminacin del programa y su vuelta al entorno de ejecucin. Aunque su descripcin correspondera formalmente al captulo destinado a la Librera Estndar, por la razn ya sealada ( 1.5 [1]) incluiremos aqu una descripcin de las mismas.

2 exit() Esta funcin de la Librera Estndar termina la ejecucin del programa. 2.1 Sintaxis #include <stdlib.h> void exit(int status); 2.2 Descripcin la funcin exit termina el proceso que la ha invocado. Antes de la terminacin se cierran todos los ficheros, se escriben los flujos de salida con almacenamiento temporal intermedio (buffer), y son llamadas las funciones de salida que se hayan establecido con la funcin atexit. Cuando se invoca exit desde un programa, no son invocados los destructores de ninguna variable local del mbito actual. Las variables globales son destruidas en su orden normal. El argumento status pasado por la funcin invocante se utiliza como estado de salida del proceso. La costumbre es que 0 indica terminacin normal y un valor distinto indica algn tipo de error. A este respecto C++ proporciona algunas constantes predefinidas: EXIT_FAILURE Terminacin anormal, seal al Sistema Operativo que el programa ha terminado con error. EXIT_SUCCESS Terminacin normal. 2.3 Ejemplo: #include <stdlib.h> #include <conio.h> #include <stdio.h> int main(void) { int status; puts("Introducir 1 o 2"); status = getch(); exit(status - '0'); // Sets DOS errorlevel (status - 48) return 0; // Observacin: esta linea nunca ser ejecutada }

3 atexit() Se trata de una funcin de Librera Estndar <stdlib.h> que cataloga una funcin como "funcin de salida" ( 1.5), es decir, la incluye en el lote de las que se ejecutarn justo antes del final de la ejecucin. 3.1 Sintaxis int atexit(void (_USERENTRY * func)(void)); 3.2 Descripcin El argumento void (_USERENTRY * func)(void) significa que atexit debe recibir un puntero a funcin func que no recibe argumentos y devuelve void. La funcin debe ser utilizada siguiendo la convencin de llamada _USERENTRY. Justo antes de la terminacin del programa, la funcin exit llama la funcin apuntada por func y todas las dems que hayan sido registradas como "funciones de salida", hasta un total de 32, que son ejecutadas como una pila FIFO, primera en ser registrada primera en ejecutarse. atexit devuelve un entero: 0 si la operacin se realiza correctamente y otro valor si hay error (no existe espacio para registrar la nueva funcin). 3.3 Ejemplo #include <stdio.h> #include <stdlib.h> void fun1(void) { puts("Llamada funcin de salida #1"); } void fun2(void) { puts("Llamada funcin de salida #2"); } int main(void) { atexit(fun1); atexit(fun2); return 0; } Salida: Llamada funcin de salida #1" Llamada funcin de salida #2" // aade fun1 a la terminacion // aade fun2 a la terminacin

4 abort() Esta funcin de la Librera Estndar <stdlib.h>, provoca la terminacin anormal del programa. Tngase en cuenta que abort no destruye las variables globales ni los objetos del mbito actual en el momento de su invocacin.

4.1 Sintaxis #include <stdlib.h> void abort(void); 4.2 Descripcin Esta funcin produce una terminacin anormal del programa mediante una llamada a raise(SIGABRT). Si no existe un manejador de seal paraSIGABRT, la funcin escribe un mensaje de terminacin: Abnormal program termination en el dispositivo stderr, y termina la ejecucin con una llamada a _exit con un cdigo 3, valor este que es devuelto al proceso padre o al procesador de comandos del Sistema Operativo. 4.3 Ejemplo #include <stdio.h> #include <stdlib.h> int main(void) { puts("El progrma aborta"); abort(); return 0; // Esta lnea nunca es ejecutada !! }

5 raise() Esta funcin de Librera Estndar <signal.h>, es una rutina de control de proceso que enva una seal al programa que lanz una aplicacin. Viene a ser una suerte de manejador de excepciones entre el programa padre y la aplicacin lanzada por l. 5.1 Sintaxis int raise(int sig); 5.2 Descripcin Esta funcin enva una seal sig al programa padre de la aplicacin. Si a su vez el padre ha instalado un manejador de seal para el tipo especificado por sig, se ejecuta este "handler". Si no existe, se ejecuta la accin prevista por defecto. Si la operacin tiene xito, la funcin devuelve 0; en caso contrario un valor distinto. 5.2.1 El fichero de cabecera signal.h incluye una serie de constantes predefinidas que pueden utilizarse con raise, son las que se indican: Constante SIGABRT SIGFPE SIGILL Descripcin Terminacin anormal Operacin de coma flotante errnea Instruccin ilegal

SIGINT SIGSEGV SIGTERM SIGUSR1 SIGUSR2 SIGUSR3 SIGBREAK

Interrupcin por Ctrl-C Acceso a almacenamiento no vlido Peticin de terminar el programa Sentido definido por el usuario Sentido definido por el usuario Sentido definido por el usuario Interrupcin por Ctrl-Break

5.2.2 Observacin En condiciones normales C++ Builder nunca lanza una llamada raise(SIGABRT), aunque puede ocurrir en las funcin abort, y en las excepciones imprevistas ( 1.6.3). Bajo Windows-32 todas las llamadas a raise utilizando estas constantes, excepto las definidas por el usuario, abortan la ejecucin del programa en el punto de llamada. Por su parte raise(SIGABRT) enva un mensaje al entorno de ejecucin: Abnormal program termination. Ejemplo: #include <signal.h> int main(void) { int a = 10, b = 0; if (b == 0) raise(SIGFPE); // previene error de dividir por cero a = a / b; return 0; }

1.5.2 Soporte de runtime


1 Sinopsis El lenguaje C++ dispone de una serie de utilidades diseadas para ofrecer al programador soporte de tiempo de ejecucin ("Runtime"). En realidad este soporte est constituido por una serie de aadidos heterogneos de difcil clasificacin, cuyo nico punto en comn es que se refieren a "runtime". Es decir, a circunstancias que no pueden ser adelantadas o previstas en tiempo de compilacin. Algunos, como el operador typeid o el sistema de excepciones, son incorporaciones tardas que no existan en las primeras versiones del lenguaje, cuya inclusin ha sido aconsejada por la experiencia. Algunos son elementos del lenguaje, otros son utilidades incorporadas a la Librera Estndar que, como tales, no forman parte del lenguaje en sentido estricto, aunque s del C++ prctico [1].

La heterogeneidad antes mencionada, hace que su descripcin se encuentre dispersa a lo largo de esta obra, de modo que su inclusin en este captulo solo es a ttulo de presentacin, con el fin de ofrecer al lector una visin sinptica de su conjunto. Las principales incorporaciones de C++ en este sentido, han sido el mecanismo de excepciones; el soporte RTTI, y una serie de funciones y clases que proporcionan determinadas funcionalidades en tiempo de ejecucin. Sistema RTTI: Estas siglas se refieren al mecanismo C++ que permite determinar en tiempo de ejecucin el tipo de un objeto. Es generalmente conocido por su acrnimo ingls RTTI ("Run time type identification"). Es una parte importante del mecanismo de comprobacin de tipos del lenguaje ( 4.9.14). Sistema de excepciones: Un sistema que permite controlar circunstancias excepcionales. Por ejemplo, errores imprevistos ( 1.6). Clases: Un conjunto de clases, de las que el sistema instancia objetos como vehculo para proporcionar informacin de utilidad en tiempo de ejecucin (la mayora de las veces informacin sobre circunstancias errneas). Son las siguientes: o bad_alloc: Tipo de la excepcin lanzada por el operador new cuando se produce un error ( 4.9.20d) o bad_cast: Tipo de la excepcin lanzada por el operador de modelado dynamic_cast cuando fracasa la conversin de una referencia ( 4.9.9c) o bad_typeid: Tipo de la excepcin lanzada por el operador typeid cuando se produce un error ( 4.9.14). o type_info: Objeto devuelto por el operador typeid, que permite conocer el tipo de un objeto ( 4.9.14a). Funciones: o set_new_handler: Es una funcin de la Librera Estndar que permite instalar un manejador definido por el usuario (una funcin) para el caso de que el operador new fracase en su intento de construir un objeto. ( 4.9.20d) o set_terminate: Es una funcin similar a la anterior. Permite instalar un manejador de usuario (funcin) que ser invocada cuando ocurra una excepcin que no tiene definido un manejador especfico ( 1.6.3). o set_unexpected: Funcin anloga a las anteriores. Permite instalar un manejador que ser utilizado cuando aparezca una excepcin imprevista ( 1.6.3). o terminate: Es una funcin de la Librera Estndar que funciona como valor por defecto de set_terminate. Es decir, si ocurre una excepcin que no tiene un manejador "Handler" especfico, y tampoco se ha instalado alguno por defecto con set_terminate, el sistema utiliza esta funcin (que a su vez se limita a invocar a abort() 1.6.3) o unexpected : Esta funcin es a set_unexpected lo que terminate a set_terminate. Es decir, si ocurre una excepcin imprevista para la que no se ha instalado ningn manejador por defecto con ser_unexpected, se invoca esta funcin ( que a su vez invoca aterminate, que invoca a abort 1.6.3).

1.6 Tratamiento de excepciones


'La respuesta real es hacer copias de seguridad de todo, con mucho cuidado y muy frecuentemente, ponindose en lo peor, ya que la definicin informtica de "lo peor" es "solo cuestin de tiempo"'. Paul Somerson "PC Magazine: DOS Powers Tools".

1 Introduccin El problema de la seguridad es uno de los clsicos quebraderos de cabeza de la programacin. Los diversos lenguajes han tenido siempre que lidiar con el mismo problema: Qu hacer cuando se presenta una circunstancia verdaderamente imprevista? (por ejemplo un error). El asunto es especialmente importante si se trata de lenguajes para escribir programas de "Misin crtica"; digamos por ejemplo controlar los ordenadores de una central nuclear o de un sistema de control de trfico areo. Antes que nada, digamos que en el lenguaje de los programadores C++ estas "circunstancias imprevistas" reciben el nombre de excepciones, por lo que el sistema que implementa C++ para resolver estos problemas recibe el nombre de manejador de excepciones. As pues, las excepciones son condiciones excepcionales que pueden ocurrir dentro del programa durante su ejecucin. Por ejemplo, que ocurra una divisin por cero, se agote la memoria disponible, Etc. que requieren recursos especiales para su control. En este captulo trataremos del manejador de excepciones C++; una serie de tcnicas que permiten formas normalizadas de manejar los errores, intentando anticiparse a los problemas potenciales previstos e imprevistos. As como permitir al programador reconocerlos, fijar su ubicacin y corregirlos.

2 Manejo de excepciones en C++ El manejo de excepciones C++ se basa en un mecanismo cuyo funcionamiento tiene tres etapas bsicas: 1: Se intenta ejecutar un bloque de cdigo y se decide qu hacer si se produce una circunstancia excepcional durante su ejecucin. 2: Se produce la circunstancia: se "lanza" una excepcin (en caso contrario el programa sigue su curso normal). 3: La ejecucin del programa es desviada a un sitio especfico donde la excepcin es "capturada" y se decide que hacer al respecto.

Pero que es eso de "lanzar" y "capturar" una excepcin"? En general la frase se usa con un doble sentido: Por un lado es un mecanismo de salto que transfiere la ejecucin desde un punto (que "lanza" la excepcin) a otro dispuesto de antemano para tal fin (que "captura" la excepcin). A este ltimo se le denomina manejador o "handler" de la excepcin. Adems del salto -como un goto-, en el punto de lanzamiento de la excepcin se crea un objeto, a modo de mensajero, que es capturado por el "handler" (como una funcin que recibe un argumento). El objeto puede ser cualquiera, pero lo normal es que pertenezca a una clase especial definida al efecto, que contiene la informacin necesaria para que el receptor sepa qu ha pasado; cual es la naturaleza de la circunstancia excepcional que ha "lanzado" la excepcin [6].

Para las tres etapas anteriores existen tres palabras clave especficas: try, throw y catch. El detalle del proceso es como sigue.

2.1 Intento ( try ). En sntesis podemos decir que el programa se prepara para cierta accin, decimos que "lo intenta". Para ello se especifica un bloque de cdigo cuya ejecucin se va a intentar ("try-block") utilizando la palabra clave try. try { ... } // bloque de cdigo-intento

El juego consiste en indicar al programa que si existe un error durante el "intento", entonces debe lanzar una excepcin y transferir el control de ejecucin al punto donde exista un manejador de excepciones ("handler") que coincida con el tipo lanzado. Si no se produce ninguna excepcin, el programa sigue su curso normal. De lo dicho se deduce inmediatamente que se pueden lanzar excepciones de varios tipos y que pueden existir tambin receptores (manejadores) de varios tipos; incluso manejadores "universales", capaces de hacerse cargo de cualquier tipo de excepcin. A la inversa, puede ocurrir que se lance una excepcin para la que no existe manejador adecuado, en cuyo caso... (la solucin ms adelante). As pues, try es una sentencia que en cierta forma es capaz de especificar el flujo de ejecucin del programa. Un bloque-intento debe ser seguido inmediatamente por el bloque manejador de la excepcin.

2.2 Se lanza una excepcin ( throw ). Si se detecta una circunstancia excepcional dentro del bloque-intento, se lanza una excepcin mediante la ejecucin de una sentencia throw. Por ejemplo: if (condicion) throw "overflow"; Es importante advertir que, salvo los casos en que la excepcin es lanzada por las propias libreras C++ (como consecuencia de un error 1.6.1a), estas no se lanzan espontneamente. Es el programador el que debe utilizar una sentencia (generalmente condicional) para, en su caso, lanzar la excepcin. El lenguaje C++ especifica que todas las excepciones deben ser lanzadas desde el interior de un bloque-intento y permite que sean de cualquier tipo. Como se ha apuntado antes, generalmente son un objeto (instancia de una clase) que contiene informacin. Este objeto es creado y lanzado en el punto de la sentencia throw y capturado donde est la sentencia catch. El tipo de informacin contenido en el objeto es justamente el que nos gustara tener para saber que tipo de error se ha producido. En este sentido puede pensarse en las excepciones como en una especie de correos que transportan informacin desde el punto del error hasta el sitio donde esta informacin puede ser analizada.

2.3 La excepcin es capturada en un punto especfico del programa ( catch ). Esta parte del programa se denomina manejador ("handler"); se dice que el "handler" captura la excepcin. El handler es un bloque de cdigo diseado para manejar la excepcin precedido por la palabra catch. El lenguaje C++ requiere que exista al menos un manejador inmediatamente despus de un bloque try. Es decir, se requiere el siguiente esquema: try { // bloque de cdigo que se intenta ... } catch (...) { // bloque manejador de posibles excepciones ... } ... // continua la ejecucin normal

El "handler" es el sitio donde continua el programa en caso de que ocurra la circunstancia excepcional (generalmente un error) y donde se decide qu hacer. A este respecto, las estrategias pueden ser muy variadas (no es lo mismo el programa de control de un reactor nuclear que un humilde programa de contabilidad). En ltimo extremo, en caso de errores absolutamente irrecuperables, la opcin adoptada suele consistir en mostrar un mensaje explicando el error. Puede incluir el consabido "Avise al proveedor del programa" o bien generar un fichero texto (por ejemplo:error.txt) con la informacin pertinente, que se guarda en disco con objeto de que pueda ser posteriormente analizado y corregido en sucesivas versiones de la aplicacin [2]. Llegados a este punto debemos recordar que, como veremos en los ejemplos, las excepciones generadas pueden ser de diverso tipo (segn el tipo de error), y que tambin pueden existir diversos manejadores. De hecho se debe incluir el manejador correspondiente a cada excepcin que se pueda generar.

3 Resumen Hemos dicho que try es una sentencia que en cierta forma es capaz de especificar el flujo de ejecucin del programa; en el fondo el mecanismo de excepciones de C++ funciona como una especie de sentencia if ... then .... else, que tendra la forma: If { este bloque se ejecuta correctamente } then seguir la ejecucin normal de programa else // tres acciones sucesivas. a. Crear un objeto con informacin del suceso (excepcin) b. Transferir el control de ejecucin al "handler" correspondiente c. Recibir el objeto para su anlisis y decisin de la accin a seguir en este caso la sintaxis utilizada es la siguiente: try { // bloque de cdigo que se intenta ... } catch (...) { // captura de excepciones

... } ...

// continua la ejecucin normal

El diseo del mecanismo de excepciones C++, someramente expuesto, tiene la ventaja de permitir resolver una situacin muy frecuente: el bloque en que se detecta el error no sabe que hacer en tal caso (cuando se presenta el error o excepcin); la accin depende en realidad de un nivel anterior, el mdulo que invoc la operacin. Como decimos, esta situacin es muy frecuente ( ), entre otras razones porque si un mdulo pudiera anticipar un error por si mismo, tambin podra evitarlo, con lo que no habra necesidad de mecanismo de excepciones. Esta circunstancia es especialmente patente en el caso de libreras, en las que el autor generalmente no sabe ni puede hacer nada al respecto de ciertos errores a excepcin de informar al usuario. Lo anterior no es bice para que, como buena prctica de programacin, se intente la captura sistemtica de errores lo ms prximo posible a su identificacin, dejando el mecanismo de excepciones para las situaciones realmente imprevisibles. Por ejemplo, siempre que sea posible: if (b == 0) { /* alguna accin... */; } else x = a/b;

4 Precauciones Cuando se plantean este tipos de cuestiones de seguridad surge inevitablemente una pregunta: Que sucede si se producen errores (circunstancias excepcionales) durante el proceso de control de excepciones?. La respuesta ms honesta es que el sistema perfecto e invulnerable no existe. Aunque el sistema de excepciones de C++ es una formidable herramienta para controlar imprevistos, que permite hasta cierto punto, controlar imprevistos dentro de imprevistos. A pesar de ello, nada puede sustituir a una programacin cuidadosa. El propio Stroustrup advierte: "Aunque las excepciones se pueden usar para sistematizar el manejo de errores, cuando se adopta este esquema, debe prestarse atencin para que cuando se lance una excepcin no cause ms problemas de los que pretende resolver. Es decir, se debe prestar atencin a la seguridad de las excepciones. Curiosamente, las consideraciones sobre seguridad del mecanismo de excepciones conducen frecuentemente a un cdigo ms simple y manejable". Comentarios sobre la idoneidad del mecanismo de excepciones ( 1.6W2).

5 Tipos de excepciones Durante la ejecucin de un programa pueden existir dos tipos de circunstancias excepcionales: sncronas y asncronas. Las primeras son las que ocurren dentro del programa. Por ejemplo, que se agote la memoria o cualquier otro tipo de error. Son a estas a las que nos hemos estado refiriendo. En la introduccin (1 ) hemos indicado: "las excepciones son condiciones excepcionales que pueden ocurrir dentro del programa... " y las nicas que se consideran. Las excepciones asncronas son las que tienen su origen fuera del programa, a nivel del Sistema Operativo. Por ejemplo que se pulsen las teclas Ctrl+C. Generalmente las implementaciones C++ solo consideran las excepciones sncronas, de forma que no se pueden capturar con ellas excepciones tales como la pulsacin de una tecla. Dicho con otras

palabras: solo pueden manejar las excepciones lanzadas con la sentencia throw. Siguen un modelo denominado de excepciones sncronas con terminacin, lo que significa que una vez que se ha lanzado una excepcin, el control no puede volver al punto que la lanz. Nota: El "handler" no puede devolver el control al punto de origen del error mediante una sentencia return. En este contexto, un returnen el bloque catch supone salir de la funcin que contiene dicho bloque.

El sistema Estndar C++ de manejo de excepciones no est diseado para manejar directamente excepciones asncronas, como las interrupciones de teclado, aunque pueden implementarse medidas para su control. Adems las implementaciones ms usuales disponen de recursos para menejar las excepciones del Sistema Operativo. Por ejemplo, C++ Builder dispone de los mecanismos adecuados para manejar excepciones de Windows-32 (asncronas) a travs de su librera VCL [1] ( 1.6w1).

6 Secuencia de ejecucin Como puede verse, la filosofa C++ respecto al manejo de excepciones no consiste en corregir el error y volver al punto de partida. Por el contrario, cuando se genera una excepcin el control sale del bloque-intento try que lanz la excepcin (incluso de la funcin), y pasa al bloquecatch cuyo manejador corresponde con la excepcin lanzada (si es que existe). A su vez el bloque catch puede hacer varias cosas: Relanzar la misma excepcin ( 1.6.1). Saltar a una etiqueta ( 1.6.2) Terminar su ejecucin normalmente (alcanzar la llave } de cierre).

Si el bloque-catch termina normalmente sin lanzar una nueva excepcin, el control se salta todos los bloques-catch que hubiese a continuacin y sigue despus del ltimo. Puede ocurrir que el bloque-catch lance a su vez una excepcin. Lo que nos conduce a excepciones anidadas. Esto puede ocurrir, por ejemplo, cuando en el proceso de limpieza de pila ("Stack unwinding") que tienen lugar tras una excepcin, un destructor lanza una excepcin . Como se ver en los ejemplos, adems del manejo y control de errores, las excepciones C++ pueden utilizarse como un mecanismo de returno break multinivel, controlado no por una circunstancia excepcional, sino como un acto deliberado del programador para controlar el flujo de ejecucin [5]. Ejemplos: ( 4.5.8).

7 Constructores y destructores en el manejo de excepciones Cuando en el lanzamiento de excepciones se utilizan objetos por valor, throw llama al constructor copia ( 4.11.2d4). Este constructor inicializa un objeto temporal en el punto de lanzamiento.

Si ocurren errores durante la construccin de un objeto, los constructores pueden lanzar excepciones [3]. Si un constructor lanza una excepcin, el destructor del objeto no es llamado necesariamente. Cada vez que desde dentro de un bloque-try se lanza una excepcin y el control sale fuera de bloque, tiene lugar un proceso de bsqueda y desmontaje descendente en la pila hasta encontrar el manejador ("Catcher") correspondiente. Durante este proceso, denominado "Stack unwinding", todos los objetos de duracin automtica que se crearon hasta el momento de ocurrir la excepcin, son destruidos de forma controlada mediante llamadas a sus destructores. Si uno de los destructores invocados provoca a su vez una excepcin que no tiene un "handler" adecuado, se produce una llamada a la funcin terminate ( 1.6.3). Nota: Observe que no se menciona para nada la destruccin de objetos persistentes que se hubiesen creado entre el inicio del bloque tryy el punto de lanzamiento de la excepcin. Esto origina una difcil convivencia entre el mecanismo de excepciones y el operador new. Para resolver los problemas potenciales deben adoptarse precauciones especiales ( 4.9.20).

La invocacin a los destructores de los objetos automticos, se realiza solo para aquellos objetos que hubiesen sido construidos totalmente a partir de la entrada en el bloque-intento (objetos cuyos constructores hubiesen finalizado satisfactoriamente). Si los objetos tienen sub-objetos, la invocacin solo se realiza para los destructores de la clase-base. Tema relacionado: Control de recursos ( 4.1.5a)

Nota: En el caso del C++Builder, los destructores son llamados por defecto, pero puede evitarse mediante la opcin -xd- del compilador como se indica a continuacin. Esta opcin, como otras de este tipo, est gobernada por el valor de una constante global ( 1.4.1a).

8 Establecer opciones de manejo de excepciones Los compiladores suelen disponer de comandos de compilacin ( 1.4.3) para determinar el tratamiento que seguir el manejo de excepciones. A ttulo de ejemplo se muestran algunos: Opcin -xCompilador Descripcin Borland C++ Deshabilitar el manejo de excepciones C++ (habilitado por defecto). Nota: la eliminacin del mecanismo de excepciones en nuestro cdigo no impide que estas puedan ser utilizadas en aquellas funciones de librera que lo tienen habilitado y sean enlazadas con nuestro ejecutable. Por ejemplo, las rutinas de tiempo real de Borland. -xd Borland C++ Habilita limpieza total. Llamada a los destructores para todos los objetos declarados automticos (locales) entre el mbito del capturador (catch) y el lanzador (throw) de la excepcin cuando es lanzada la excepcin (activo por defecto). Si se activa esta opcin, tambin hay que activar la opcin RT ( 4.9.14).

-xp

Borland C++

Habilita informacin sobre las excepciones. Posibilita identificacin de excepciones en tiempo de ejecucin mediante la inclusin en el objeto del nmero de lneas del cdigo fuente. Esto permite al programa interrogar el fichero y nmero de lnea donde ha ocurrido una excepcin utilizando los identificadores globales __ThrowFileName y __ThrowLineNumber. Deshabilitar el mecanismo de excepciones. Nota: los compiladores gcc deshabilitan esta opcin por defecto para aquellos lenguajes que normalmente no utilizan excepciones (C por ejemplo), pero lo habilitan por defecto para los que, como C++, suelen utilizarlo.

-fnoGNU c++ exceptions

1.6.1 Lanzar una excepcin


Nota: La comprensin de los detalles contenidos en este epgrafe y siguientes, relativos al mecanismo de excepciones C++, requiere un conocimiento previo de las clases ( 4.11), por lo que aconsejamos al estudiante que realiza una primera lectura, seguir ahora con otros tpicos y volver cuando tenga conocimiento de ellas.

1 Sinopsis El bloque en que queramos controlar una circunstancia excepcional (error) se seala al compilador precedindolo de la palabra clave try. Lo denominamos bloque-intento ("Try block"), y como cualquier otro bloque estar delimitado por un corchetes { } ( 3.2.6): try { ... } // bloque-intento

Durante la ejecucin de este bloque, el proceso sigue los siguientes pasos: a: Se produce una circunstancia excepcional: Se lanza la excepcin sealada por la sentencia throw El programa busca por un manejador ("handler") adecuado a la excepcin a1 Se encuentra el "handler" La pila (stack) es recorrida hacia abajo hasta el punto donde est el manejador. El control del programa es transferido al manejador. a2 No se encuentra ningn manejador ( Se invoca la funcin terminate() a2a Se ha establecido una funcin t_func por defecto con set_terminate(). terminate invoca t_func (que debe terminar el programa). a2b No se ha establecido ninguna funcin por defecto con set_terminate() terminate invoca la funcin abort(). b: No se produce ninguna excepcin:

1.6.3 Excepciones imprevistas).

El programa sigue su ejecucin normal (saltndose los bloques catch que pudieran seguir).

2 Se lanza una excepcin Hemos sealado ( 1.6) que, salvo los casos en que son lanzadas por las propias libreras C++ (como consecuencia de algn error), las excepciones no se lanzan espontneamente. Es el programador el que debe incluir una sentencia (generalmente condicional) para lanzar la excepcin en su caso. Las excepciones se lanzan mediante una sentencia throw que obligatoriamente debe estar situada en el interior de un bloque try (o en un bloque o funcin anidada en l). Tambin obligatoriamente debe seguir, al menos un bloque catch ( 1.6.2). Sintaxis: throw expresin-de-asignacin; Ejemplo (las dos sentencias son equivalentes): throw Obj(); throw (Obj o); El lenguaje C++ permite que lanzar excepciones de cualquier tipo. Pero generalmente son instancias de una clase tipoX, que contiene la informacin necesaria para conocer la naturaleza de la circunstancia excepcional (probablemente un error). El tipoX debe corresponder con el tipo de argumento usado en el bloque catch. Nota: Se recomienda que las clases diseadas para instanciar este tipo de objetos sean especficas y diseadas para este fin exclusivo, sin que tengan otro uso que la identificacin y manejo de las excepciones.

La expresin throw (X arg) inicializa un objeto temporal arg de tipo X, aunque puede que se generen otras copias por necesidad del compilador. En consecuencia puede ser til definir un constructor-copia ( 4.11.2d4) cuando el objeto a lanzar pertenece a una clase que contiene subobjetos.

Ejemplo 1: En el siguiente ejemplo se pasa el objeto Out al manejador catch de la lnea 11. #include <stdio.h> bool pass; class Out{}; void festival(bool); int main() { try { pass = true; festival(true); } catch(Out o) { aqu)

// // // //

L.3: Para instanciar el objeto a lanzar L.4: prototipo =================== L.7: bloque intento

// L.11: manejador (la excepcin es capturada

pass = false; // L.12: valor de pass si se produce una excepcin } // L.13: return pass ? (puts("Acierto!"),0) : (puts("Fallo!"),1); // L.14: } void festival(bool firsttime){ if(firsttime) {Obj o; throw o; } } Salida: Fallo! Comentario: El bloque-intento try que comienza en L.7 y el manejador catch que comienza en L.11, forman el mecanismo de excepcin junto con la clase Out que sirve para instanciar el objeto que se lanzar si se produce una excepcin. Cualquier excepcin lanzada dentro del bloque try ser capturada en la lnea 11 (si es del tipo adecuado). Observese que catch y su bloque (L11-13) son en realidad como una funcin que devolviera void y que debe recibir un tipo Out. El control ser transferido a L.11 si la excepcin es del este tipo, lo que efectivamente sucede, ya que la sentencia de L.18 indica que la excepcin lanzada o, es un objeto tipo Out. Despus de ejecutado el bloque catch el control del programa sigue en L.14. Observe que la sentencia L.18 podra haberse sustituido por: if(firsttime) throw Out(); // L.18a: Lanzar excepcin. // L.17: definicin de festival // L.18: lanzar excepcin

ms compacta pero equivalente, ya que Out(), es una invocacin al constructor por defecto de la clase ( 4.11.2d1), que produce un objeto exactamente igual al objeto o producido en la sentencia Obj o. [1] Observe tambin que en este caso no se trata propiamente de un error, puesto que la excepcin es lanzada inevitablemente. Aqu el mecanismo de excepcin se utiliza ms bien como un sistema de return multinivel.

3 Relanzar una excepcin Si se ha lanzado previamente una excepcin y se est en el bloque que la ha capturado, es posible repetir el lanzamiento de la excepcin (el mismo objeto recibido en el bloque catch) utilizando simplemente la sentencia throw sin ningn especificador. No perder de vista que el lanzamientothrow, solo puede realizarse desde el interior de un bloque try, al que debe seguir su correspondiente "handler". Ejemplo (ver tambin un ejemplo ejecutable try { ... ):

if (x) throw A(); } catch (A a) { ... throw; } Versin correcta: try { ... if (x) throw A(); } catch (A a) { ... try { throw; } catch (A a) { ... } }

// lanzar excepcin // capturar excepcin // hacer algo respecto al error // Error!! no est en un bloque try

// lanzar excepcin // capturar excepcin // hacer algo respecto al error // Ok. relanza excepcin A // capturar excepcin

Otra versin correcta: void foo(); void main () { try { ... foo(); } catch (A a) { ... } return 0; } void foo() { ... if (x) try { throw A(); } catch (A) { ... throw; } } Comentario La excepcin L.15 es capturada en L.17. La sentencia L.19 es correcta porque este throw est en el bloque T1, y relanza la excepcin A lanzada en L.15. La excepcin lanzada en L.19 es capturada en L.7, de forma que se ejecutan las sentencias L.8.

// ========== // Bloque-T1

// L.7: capturar excepcin // L.8: hacer algo respecto al error

// Bloque-T2 // L.15: Ok. lanza excepcin A // L.17: // hacer algo respecto al error // L.19: Ok. relanzar excepcion A

Ver ejemplo ejecutable (

Relanzar excepcin)

Ejemplo 2: Muestra como el manejador de una excepcin puede relanzar la excepcin recibida (en ese momento debe existir una excepcin en curso). #include <iostream> using namespace std; bool ok = true; class A{}; void test(); void foo(bool);

// prototipo // prototipo

int main() { // ================== try { test(); } catch(A& a){ ok = true; } ok ? (cout << "Correcto\n") : (cout << "Incorrecto\n"); return ok ? 1 : 0; } void test() { try { foo(true); } catch(A& a) { ok ? (cout << "Correcto\n") : (cout << "Incorrecto\n"); ok = false; throw; // se relanza la excepcin A } } void foo(bool b){ b ? (cout << "Lanzar A\n") : (cout << "No lanzar A\n"); if(b) throw A(); } Salida: Lanzar A Correcto Correcto

Ejemplo 3: Cuando ocurre una excepcin la expresin throw inicializa un objeto temporal del tipo X correspondiente al argumento utilizado en la expresinthrow(X arg). El compilador puede generar otras copias, en consecuencia, como se muestra aqu, puede ser til definir un constructor de copias para el objeto usado en la excepcin. #include <stdio.h> class festival { public: festival() { // constructor por defecto puts("Hacer un festival");

} festival(const festival&){ // constructor copia puts("Copiando un festival"); } ~festival() { // destructor puts("Destruyendo un festival"); } }; int main() { // ================= try { puts("Lanzando un festival"); throw(festival()); } catch(festival&){ puts("Capturando un festival" ); } return 0; } Salida: Lanzando un festival Hacer un festival Copiando un festival Destruyendo un festival Capturando un festival Destruyendo un festival

1.6.1a Excepciones en la Librera Estndar


1 Sinopsis Ciertos elementos del lenguaje y algunas utilidades de la Librera Estndar ( 5) pueden lanzan excepciones para sealar condiciones de error. Los objetos lanzados pertenecen a algn miembro de una jerarqua de clases que tiene el siguiente diseo: exception logic_error domain_error invalid_argument length_error out_of_range runtime_error range_error overflow_error underflow_error bad_alloc bad_cast bad_exception bad_typeid

Como puede verse, todas ellas derivan de la superclase exception, definida en la cabecera <stdexcept>, que tiene la siguiente interfaz: class exception { public: exception () throw(); exception (const exception&) throw(); exception& operator= (const exception&) throw(); virtual ~exception () throw(); virtual const char* what () const throw(); }; Esta clase tiene cinco mtodos pblicos, ninguno de los cuales puede lanzar una excepcin. El ms interesante es what(), que generalmente devuelve una descripcin textual del error causante de la excepcin.

Un logic_error seala una inconsistencia en la lgica interna del programa, o la violacin de cierta precondicin en la parte del software cliente (una rutina de usuario). Por ejemplo, el mtodo substr de la clase estndar string, lanza una excepcin del tipo out_of_range si se interroga una subcadena (substring) situado ms all del final de la cadena. Interfaz: class logic_error : public exception { public: explicit logic_error (const string& what_arg); }; De esta clase derivan las siguientes: class domain_error : public logic_error { public: explicit domain_error (const string& what_arg); }; class invalid_argument : public logic_error { public: explicit invalid_argument (const string& what_arg); }; class length_error : public logic_error { public: explicit length_error (const string& what_arg); }; class out_of_range : public logic_error { public: explicit out_of_range (const string& what_arg); };

Los runtime_error son aquellos que no pueden ser fcilmente previstos por anticipado, y

generalmente se deben a causas externas al programa. Por ejemplo, la ocurrencia de un overflow aritmtico como consecuencia de procesar argumentos que son perfectamente legales para una funcin. class runtime_error : public exception { public: explicit runtime_error (const string& what_arg); }; De aqu derivan las siguientes: class range_error : public runtime_error { public: explicit range_error (const string& what_arg); }; class overflow_error : public runtime_error { public: explicit overflow_error (const string& what_arg); }; class underflow_error : public runtime_error { public: explicit underflow_error (const string& what_arg); };

La excepcin bad_alloc se lanza cuando se agota la memoria disponible en el montn ( 4.9.20d). Tambin deriva pblicamente de la claseexception.

La excepcin bad_cast es generada cuando fracasa un modelado dynamic_cast al ser aplicado a una referencia ( 4.9.9c). Responde a la siguiente interfaz: class bad_exception : public exception { public: bad_exception() throw(); bad_exception(const bad_exception&) throw(); bad_exception& operator=(const bad_exception&) throw(); virtual ~bad_exception() throw(); virtual const char* what() const throw(); }; Cuando desde una funcin se pretende lanzar una excepcin no prevista (de un tipo no incluido en su especificador de excepciones 1.6.4), la excepcin se convierte en el tipo bad_exception.

La excepcin bad_typeid es lanzada cuando se intenta aplicar el operador typeid ( 4.9.14) a una expresin nula. class bad_typeid : public exception { public: bad_typeid() throw(); bad_typeid(const bad_typeid&) throw();

bad_typeid& operator=(const bad_typeid&) throw(); virtual ~bad_typeid() throw(); virtual const char* what() const throw(); }; Ver ejemplo de bad_typeid ( 4.9.14).

2 Las sentencias en que se puedan recibir excepciones de librera [1], deben estar inexcusablemente incluidas en un bloque try. En caso contrario, de producirse un error, el programa terminar sin ms ceremonial que un mensaje: Abnormal program termination. Como puede verse en sus declaraciones, las clases logic_error, runtime_error y las que derivan de ellas, tienen un constructor explicit ( 4.11.2d1) que acepta la forma genrica: explicit exception_class (const string& what_arg);

Aqu se puede incluir como argumento una cadena alfanumrica explicativa del error, que ser ms tarde devuelta por el mtodo what . La forma normal de usarlo es la siguiente: try { ... if ( condicion ) { throw runtime_error("runtime num. xxxx");
}

} catch (const exception& e) { // captura todas las excepciones de la jerarqua cout << "Error: " << e.what(); }

3 Por las razones sealadas al tratar de la captura de excepciones ( 1.6.2), la captura discriminada de este tipo de excepciones requieren comenzar siempre por la clase ms derivada. Ejemplo sin discriminacin del tipo de error producido: try { ... } catch (...) { ... } // posible lanzamiento // captura

Ejemplo con poca discriminacin: try { ... } catch (bad_alloc){...}

catch catch catch catch ...

(bad_cast){...} (bad_exception){...} (bad_typeid){...} (exception) {...}

Este otro proporciona ms informacin: try { ... } catch (logic_error) {...} catch (runtime_error) {...} catch (bad_alloc){...} catch (bad_cast){...} catch (bad_exception){...} catch (bad_typeid){...} ... Mejor este otro con la mxima discriminacin: try { ... } catch ( domain_error) {...} catch ( invalid_argument) {...} catch ( length_error) {...} catch ( out_of_range) {...} catch ( range_error) {...} catch ( overflow_error) {...} catch ( underflow_error) {...} catch (bad_alloc){...} catch (bad_cast){...} catch (bad_exception){...} catch (bad_typeid){...} ...

Ejemplos relacionados: Capturar excepciones en jerarqua de clases ( 1.6.2)

1.6.2 Capturar excepciones


1 Sinopsis Recordemos ( 1.6) que el manejador de excepciones ("handler") es un bloque de cdigo precedido por la palabra catch. Este bloque debe seguir inmediatamente el bloque-intento try o a otro bloque catch segn el siguiente esquema:

try { ... } catch (TipoX x) { ... } catch (TipoY y) { ... } ... La sintaxis es:

// bloque-intento // posibles errores // capturar errores X // capturar errores Y // sigue aqu

catch (<tipo_exc> [<nombre>]) { <sentencias> }

<tipo_exc> es el tipo de excepcin que se capturar en esta sentencia. Ejemplo: try { ... if (x > limit) throw "Overflow"; } catch (char*) { cout << "Recibido error: "; } Eventualmente se puede aadir un identificador <nombre>, que puede ser usado en el cuerpo <sentencias> del bloque, de forma anloga a los argumentos de las funciones. Ejemplo: struct E{ char* msg; }; ... try { ... if (x > limit) { E e = { "Error desconocido" }; throw e; } } catch (E r) { cout << "Recibido: " << r.msg << endl; }

Segn el objeto capturado sea recibido por valor o por referencia, la forma del bloque catch es alguna de las siguientes: catch(T t) {...} catch(const T t) {...} catch(T& t) {...} catch(const T& t) {...}

// esta es la ms usual

Nota: aunque nos referimos a l como bloque-catch, en realidad su comportamiento y su estructura son muy parecidos al de una funcin. Aunque con diferencias, para muchos

aspectos podemos pensar en l como una autntica funcin. Como veremos a continuacin, tambin aqu se establecen ciertas reglas de "congruencia de argumentos" para ver que bloque-catch responde a una excepcin determinada.

2 Concordancia Debe existir un manejador para cada excepcin distinta que pueda lanzar el programa. El manejador captura una excepcin cuando el tipo de esta coincide (segn ciertas reglas ) con el tipo de <tipo_exc>. Una vez que se ha producido la concordancia, la pila es descargada hasta el punto del "handler" al que se transfiere el control. Es entonces el manejador el que decide el tratamiento adecuado a la anormalidad detectada. Ejemplo: try { // bloque-intento ... // posibles errores if (...) throw Overflow; ... if (...) throw Aritmetic; ... } catch (Overflow, o) { // capturar errores ... } catch (Aritmetic, a) { // capturar errores ... } ... // sigue aqu si no

overflow o aritmetic

overflow aritmticos hay errores

2.1 En caso de no existir un manejador adecuado a una excepcin determinada, se desencadena un protocolo que, por defecto, produce sin ms la finalizacin del programa ( 1.6.3 Excepciones imprevistas).

2.2 Reglas de concordancia La excepcin es capturada por el bloque-catch cuyo argumento coincida con el tipo de objeto lanzado por la sentencia throw. La bsqueda de coincidencia se realiza sucesivamente sobre los bloques catch en el orden en que aparecen en el cdigo hasta que aparece la primera concordancia. Despus que se ejecuta este bloque, el programa contina su ejecucin despus del ltimo de los manejadores que sigan al bloquetry que lanz la excepcin, sin que se realice ulterior evaluacin de otros posibles manejadores para la excepcin lanzada. Por consiguiente, el orden de colocacin de los bloques catch es determinante. Por ejemplo: si se incluye un manejador universal , debera ser el ltimo. La concordancia sigue ciertas reglas [1]. El objeto e lanzado por la sentencia throw E() ser capturado por un bloque catch(C c) si se cumplealguna de las siguientes condiciones: E y C son del mismo tipo.

C es una super-clase de E visible en el punto de lanzamiento de la excepcin. Por esta razn, cuando se captura una excepcin y esta pertenece a una jerarqua de clases, hay que comenzar por capturar la clase ms derivada (ver Ejemplo ). C y E son punteros a clases de una misma jerarqua, y existe una conversin estndar de E a C ( 4.11.2b1) en el punto de lanzamiento de la excepcin.

Suponiendo que: D* E; S* C; // E es un puntero-a-tipoD // C es un puntero-a-tipoS

Para que exista una conversin estndar de E a C , debe cumplirse alguna de las condiciones siguientes: C es del mismo tipo que E, aunque puede tener un especificador const o volatile. C es un puntero-a-void (tipo void*). B es una superclase de D en la que sus miembros pueden referenciar sin ambigedad a los de B. (esto solo es de aplicacin en los casos de herencia mltiple).

Nota: De la segunda condicin se deduce que cualquier excepcin E capturada por un puntero puede ser tambin capturada por un puntero-a-void (el tipo void* funciona como un capturador universal de punteros).

La norma subyacente bajo las condiciones anteriores es que E y C deben coincidir exactamente, o la excepcin E capturada, debe ser derivada del parmetro C del "catcher". El siguiente ejemplo termina sin que se capture la excepcin ... class B {}; class C {}; void fun() { throw B; }

// se lanza un tipo B

main() { // ======== try { fun(); } catch(C) { cout << "Capturada excepcin C" << endl; abort(); } } En cambio en el que sigue si es capturada: ... class B {}; class D : public B {}; void fun() { throw D(); }

main() { // ========== try { fun(); } catch(B) { cout << "Capturada excepcin B" << endl; } }

2.3 El manejador universal: Existe la posibilidad de definir un manejador que capture cualquier excepcin mediante la sintaxis siguiente: catch (...) { <sentencias> } Ejemplo: En el programa que sigue la sentencia catch captura cualquier excepcin con independencia de su tipo. Solo existe un catch para el bloque try. #include <stdio.h> bool pass; class Out{}; void festival(bool firsttime) { if(firsttime) throw Out(); } void test() try { festival(true); } catch(...){ pass = true; } // puede capturar cualquier excepcin } int main() { // ================== pass = false; test(); return pass ? (puts("Excepcin capturada"),0) : (puts("no hay excepcin") ,1); } Salida: Excepcin capturada

3 Salto a una etiqueta Se puede utilizar un goto para transferir el control del programa fuera de un manejador. Para ilustrarlo utilizaremos una versin modificada del ejemplo ya comentado ( 1.6.1). #include <stdio.h> bool pass; class Out{}; void festival(bool);

// L.3: Para instanciar el objeto a lanzar // L.4: prototipo

int main() { // ===================== try { // L.7: bloque intento pass = true; festival(true); } catch(Out o) { // L.11: manejador (captura la excepcin) goto fallo } // L.13: return (puts("Acierto!"),0); fallo: return (puts("Fallo!"),1); } void festival(bool firsttime){ if(firsttime) throw Out(); } // L.17: definicin de festival // L.18: Lanzar excepcin.

4 Excepciones anidadas El sistema de control de excepciones puede ser anidado a cualquier nivel (pueden existir bloques try dentro de bloques try y de bloques catch) Como debe mantenerse la regla de que un bloque try debe ser seguido inevitablemente por un catch, lo anterior significa que pueden existir secuencias try-catch dentro de bloques try y de bloques catch. Secuencias anidadas en el bloque-intento. class Error { }; void func () { ... try { ... try { ... } catch { I1I1 ... } ... } catch (Error) { ... } ... }

// I1 Bloque-intento 1 // I1I1 Bloque-intento en I1 // I1H1 Handler de I1I1: capturar excepciones de

// contina bloque I1 // H1 Handler de I1: capturar excepciones de I1 // sigue a bloque I1

Secuencias anidadas en el bloque-manejador class Error { }; void func () { ... try { ...

// I1 Bloque-intento 1

} catch (Error) { ... try { ... } catch { H1I1 ... } ... } ... }

// H1 Handler de I1: capturar excepciones de I1 // H1I1 Bloque-intento en Handler-H1 // H1H1 Handler de H1I1: capturar excepciones de

// contina handler H1 // sigue a bloque I1

Ejemplo 1: #include <stdio.h> class festival{}; class Verano : public festival{}; class Primavera: public festival{}; void fiesta(int); int main() { // ==================== try { fiesta(0); } catch(const Verano&) { puts("Festival de Verano"); } catch(const Primavera&) { puts("Festival de Primavera" ); } try { fiesta(1); } catch(const Verano&) { puts("Festival de Verano"); } catch(const Primavera&) { puts("Festival de Primavera" ); } return 0; } void fiesta(int i) { if(i==1) throw( Verano() ); else throw( Primavera() ); } Resultado: Festival de Primavera Festival de Verano

Ejemplo 2: El ejemplo que sigue muestra que cuando se captura una excepcin y esta pertenece a una jerarqua de clases, hay que comenzar por la clase ms derivada, pues de lo contrario se pierde capacidad de discriminacin del tipo de excepcin ocurrido. #include <stdio.h> class festival{}; class Verano : public festival{}; class Primavera: public festival{};

void fiesta(int i) { if (i==1) throw(Verano() ); else if(i==2) throw(Primavera()); else throw(festival() ); } int main() { // ==================== try { fiesta(0); } // estas sentencias estn en el orden adecuado catch(const Verano& ) { puts("Festival de Verano"); } catch(const Primavera&){ puts("Festival de Primavera" ); } catch(const festival& ){ puts("Festival" ); } try { fiesta(1); } catch(const Verano& ) { puts("Festival de Verano"); } catch(const Primavera&){ puts("Festival de Primavera" ); } catch(const festival& ){ puts("Festival" ); } try { fiesta(2); } catch(const Verano& ) { puts("Festival de Verano"); } catch(const Primavera&){ puts("Festival de Primavera" ); } catch(const festival& ){ puts("Festival" ); } /* Si se captura la clase base primero se pierde la posibilidad de comprobar la sub-clase de la excepcin que ha sido lanzada realmente */ try { fiesta(1); } catch(const festival& ){ puts("Festival (de que tipo??!!)"); } catch(const Verano& ) { puts("Festival de Verano" ); } catch(const Primavera&){ puts("Festival de Primavera" ); } try { fiesta(2); } catch(const festival& ){ puts("Festival (de que tipo?!!!)"); } catch(const Verano& ) { puts("Festival de Verano" ); } catch(const Primavera&){ puts("Festival de Primavera" ); } return 0; } Salida: Festival Festival Festival Festival Festival

de Verano de Primavera (de que tipo??!!) (de que tipo?!!!)

Ejemplo 3 Una posible alternativa al diseo anterior permitira capturar solo las excepciones de la clase-base y utilizar las propiedades del polimorfismo ( 4.11.8) para realizar la discriminacin: #include <iostream> using namespace std;

class Festival { public: virtual void foo() { cout << "Festival" << endl; } }; class Verano : public Festival { public: void foo() { cout << "Festival de Verano" << endl; } }; class Primavera: public Festival { public: void foo() { cout << "Festival de Primavera" << endl; } }; void fiesta(int i) { if (i==1) throw(Verano() ); else if(i==2) throw(Primavera()); else throw(Festival() ); } int main() { // ==================== try { fiesta(0); } catch(Festival& f) { f.foo(); } try { fiesta(1); } catch(Festival& f) { f.foo(); } try { fiesta(2); } catch(Festival& f) { f.foo(); } return 0; } Salida: Festival Festival de Verano Festival de Primavera

1.6.3 Excepciones imprevistas


1 Sinopsis Es innecesario decir que las excepciones tambin pueden provocar excepciones; desde luego, pueden provocar mltiples errores, pero en general son de alguno de los cinco tipos que se relacionan: 1.- No existe manejador para la excepcin ("No handler for the exception"). 2.- Lanzada una excepcin no prevista ("Unexpected exception thrown") 3.- Una excepcin solo puede ser lanzada de nuevo desde un manejador ("An exception can only be re-thrown in a handler") 4.- Durante la limpieza de la pila un destructor debe manejar su propia excepcin ("During stack unwinding, a destructor must handle its own exception"). 5.- Memoria agotada ("Out of memory").

En este captulo presentamos los comportamientos adoptados por el compilador para los dos primeros casos; generalmente debidos a que no hemos diseado correctamente el sistema de manejo excepciones de nuestro programa. Algo as como el sistema de "proteccin contra fallos del sistema de emergencia". Las dos primeras situaciones errneas son independientes. Las causas/medidas-a-adoptar responde al siguiente esquema: La primera es el caso que se lance una excepcin para la que no se ha previsto un manejador adecuado; las denominamos excepciones sin manejador . En esencia el sistema consiste en que puede instalarse un manejador genrico (manejador de terminacin) que se haga cargo de la situacin si hemos olvidado instalar el "handler" adecuado para una excepcin concreta. Incluso veremos que si hemos olvidado instalar este manejador universal, el compilador proporciona uno por defecto. La segunda situacin contempla que una funcin lance una excepcin que no est incluida en su especificador de excepcin. Este concepto se explica ms adelante ( 1.6.4), por ahora adelantemos que C++ permite especificar de antemano que excepciones (tipos de objetos) podrn ser lanzados desde una funcin determinada (recordemos que en C++ todo ocurre dentro de funciones). A estas situaciones las denominamos excepciones imprevistas . Veremos que el patrn de actuacin es parecido al anterior; es posible definir para estos "imprevistos" un comportamiento (funcin) que se encargue de la situacin. En caso que no hayamos establecido nada concreto, el compilador proporciona un protocolo de actuacin por defecto.

2 Excepciones sin manejador Recordemos ( 1.6.1) que si durante la ejecucin de un bloque try se lanza una excepcin y no se encuentra ningn manejador adecuado se adoptan las siguientes medidas: Se invoca la funcin terminate() a: Se ha establecido una funcin t_func por defecto con set_terminate(). terminate invoca t_func (que debe terminar el programa). b: No se ha establecido ninguna funcin por defecto con set_terminate() terminate invoca la funcin abort(). El siguiente ejemplo muestra lo que ocurre cuando el programa encuentra una excepcin no soportada. #include <except.h> #include <process.h> #include <stdio.h> bool pass; class Out{}; void final(); void festival(bool); void test(); int main() { set_terminate(final);

// prototipo // dem. // dem. // ============= // M.1: aade final a la lista

test(); // M.2: test lanza la excepcin Out sin manejador return pass ? (puts("Salir del test"),0) : (puts("Seguir el test"),1); } void final(){ puts("*** Nadie captura la excepcin ***"); abort(); } void festival(bool firsttime){ if(firsttime) throw Out(); } void test() {festival(true); } Salida: *** Nadie captura la excepcin *** Comentario: La sentencia M.1 registra la funcin final como manejador por defecto , de forma que a partir de este momento si se lanza una excepcin que no encuentra manejador adecuado, se invocar esta funcin. M.2 invoca a test que invoca a su vez a festival con true como argumento. Lo que hace que esta ltima lance una excepcin con una instancia de Out. En el programa no existe ningn manejador especfico previsto para esta excepcin (ni para ninguna otra). En realidad no se ha previsto ningn dispositivo para manejar excepciones, no existe ningn bloque try, por lo que es invocada la funcin terminate, que invoca a su vez a la funcinfinal que se haba instalado al principio. Esta ltima es la responsable de la salida obtenida y de terminar el programa.

2.1 terminate Esta funcin de la Librera Estndar (except.h), es invocada cuando se lanza una excepcin que no encuentra el manejador adecuado. Sintaxis: void terminate(); Descripcin: La misin de esta funcin es simplemente verificar si existe alguna funcin de usuario definida como reserva para el caso de no encontrarse un manejador adecuado para la excepcin lanzada (esta funcin de reserva se denomina manejador de terminacin y se instala como se indica a continuacin). Si la funcin existe, terminate la invoca; si no existe, termnate realiza una llamada a abort ( 1.5.1), lo que origina la terminacin inmediata del programa. En otras palabras: es posible modificar la forma en que termina el programa cuando se genera una excepcin que no tiene un "handler" adecuado. Si se desea terminar con algo distinto que la

llamada a abort, se puede definir cualquier otra. Esta funcin, manejador de terminacin, ser llamada por la funcin terminate si ha sido registrada mediante set_terminate .

2.2 set_terminate set_terminate es una funcin de Librera Estndar <except.h>, que permite instalar una funcin que determina el comportamiento del programa cuando se lanza una excepcin que no encuentra ningn "handler" especfico. Podramos decir que instala un manejador por defecto (el manejador de terminacin). Sintaxis: typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler t_func); Ejemplo: set_terminate(final); ... void final(){ puts("*** Nadie captura la excepcin ***"); abort(); } Descripcin Vemos que set_terminate es una funcin que devuelve un objeto terminate_handler y recibe un argumento del mismo tipo. A su vez,terminate_hadler es un puntero a funcin que no recibe argumentos y devuelve void. La accin a ejecutar est definida por t_func, este argumento debe ser el nombre de la funcin que queremos invocar en caso de que una excepcin no encuentre un manejador adecuado. Evidentemente t_func debe responder a las expectativas, es decir: Ser una funcin que no reciba argumentos y devuelva void. Debe ser definida de forma que termine el programa. Cualquier intento de volver a su invocadora, la funcin terminate, conduce a un comportamiento indefinido del programa. Tampoco se puede lanzar una excepcin desde t_func. Si no se ha previsto ningn manejador, entonces el programa llama a la funcin terminate, que a su vez termina con una llamada a la funcinabort ( 1.5.1), y el programa termina con el mensaje: Abnormal program termination. Si se desea que se llame cualquier otra funcin distinta de abort desde terminate entonces debemos instalar nuestra propia t_func e instalarla con set_terminate, lo que nos permitira implementar cualquier accin que deseemos que no sea cubierta por abort.

3 Excepciones imprevistas

Si una funcin lanza una excepcin que no est incluida en su especificador de excepcin ( 1.6.4), se produce una llamada a la funcinunexpected , que a su vez invoca a cualquier funcin establecida por set_unexpected . Caso de no haberse establecido ninguna funcin,unexpected llama a terminate .

3.1 unexpected Esta funcin de la Librera Estndar <except.h> es invocada cuando una funcin lanza una excepcin que no est incluida en su especificador de excepcin ( 1.6.4). Sintaxis: void unexpected(); Descripcin: A su vez unexpected invoca a cualquier funcin establecida por set_unexpected ninguna funcin registrada, unexpected llama a la funcin terminate . . Si no existe

Como puede verse en su definicin, unexpected no devuelve nada, aunque a su vez puede lanzar una excepcin. Ver ejemplo ( 1.6.4)

3.2 set_unexpected Funcin de Librera Estndar <except.h>. Sintaxis typedef void ( * unexpected_handler )(); unexpected_handler set_unexpected(unexpected_handler unexp_func); unexp_func define la funcin que se pretende instalar. Descripcin Esta funcin permite instalar una funcin que ser ejecutada en caso que una funcin invoque una excepcin que no est incluida en su especificador de excepcin ( 1.6.4). Como puede verse, el argumento a utilizar es un objeto tipo unexpected_handler, es decir: un puntero a funcin que no recibe argumentos y devuelve void. En la prctica esto significa que se puede utilizar directamente el nombre de la funcin que se desea instalar, y que esta funcin debe ser del tipo adecuado (una funcin que no reciba argumentos y no devuelva nada). La funcin instalada debe ser tal que termine el programa. No debe intentar volver a su invocadora (unexpected), ya que un intento de esta ndole producira un resultado indefinido. Por

contra, unexp_func puede llamar a las funciones abort ( ).

1.5.1), exit (

1.5.1) o terminate (

4 Corolario El sistema C++ de tratamiento de errores ofrece infinitas combinaciones posibles. Cada circunstancia requiere una estrategia distinta, pero siempre deberamos instalar un sistema, aunque fuese mnimo y rudimentario, para el tratamiento de excepciones. Es mucho ms elegante salir del programa de forma controlada con un mensaje adecuado, y quizs escribiendo un fichero con el estatus y tipo de error, que terminar con un mensaje del Sistema. Como hemos visto, el compilador establece por defecto un sistema que obedece al siguiente esquema: cuando una funcin lanza una excepcin que no est incluida en su especificador de excepcin se lanza unexpected(). Si no se ha previsto otra cosa unexpected() invoca a terminate(). A su vez la accin por defecto de terminate() es invocar a abort(). Generalmente los programas tienen una vida larga y sujeta a cambios; revisiones sucesivas que los van mejorando. No existe inconveniente para que el sistema de control se vaya afinando y sofisticando a partir de un diseo inicial ms o menos rudimentario, en funcin de la experiencia obtenida con su explotacin. Como punto de partida podra servir el siguiente esquema: #include <signal.h> #include <except.h> ... void noHandler(); void imprevistas(); // excepciones sin manejador // excepciones imprevistas

int main() { // ================= set_terminate(noHandler); // aade noHandler set_unexpected(imprevistas); // aade imprevistas ... // nuestro proceso... return 0; // Ok. el programa concluye correctamente } void noHandler() { // definicin cerr << "Excepcin sin manejador. Programa terminado"; raise(SIGABRT); // El programa termina con error } void imprevistas() { // definicin cerr << "Excepcin imprevista. Programa terminado"; abort(); // El programa termina con error }

1.6.4 Especificacin de excepciones


1 Sinopsis

En C++ existe una opcin denominada especificacin de excepcin que permite sealar que tipo de excepciones puede lanzar una funcin directa o indirectamente (en funciones invocadas desde ella). Este especificador se utiliza en forma de sufijo en la declaracin de la funcin [1] y tiene la siguiente sintaxis: throw (<lista-de-tipos>) // lista-de-tipos es opcional

La ausencia de especificador indica que la funcin puede lanzar cualquier excepcin. El mecanismo de excepciones fue introducido en el lenguaje en 1989, pero la primitiva versin adoleca de un problema que podemos resumir como sigue: Supongamos que tenemos una funcin de librera cuya definicin, contenida en un fichero de cabecera, es del tipo: void somefuncion (int); Lo normal es que las "tripas" de la funcin queden ocultas al usuario, que solo dispone de la informacin proporcionada por el prototipo ( 4.4.1), pero es evidente que en estas circunstancias es imposible saber si la funcin puede lanzar una excepcin y en consecuencia, decidir si de deben tomar (o no) las medidas apropiadas para su captura. Aos despus, y ante la confusin creada, el Comit de Estandarizacin decidi incluir la especificacin de excepcionesque comentamos en este captulo. Como puede verse es un modo de incluir en el prototipo informacin suficiente para que el usuario conozca que tipo de excepciones pueden esperarse de una funcin (si es que las hay).

2 Ejemplos de funciones con especificadores de excepcin: void f1(); // f1 void f2() throw(); // f2 void f3() throw(BETA); // f3 void f4() throw(A, B*); /* f4 pblicamente de A o un puntero a puede lanzar cualquier excepcin no puede lanzar excepciones solo puede lanzar objetos BETA puede lanzar excepciones derivadas derivada pblicamente de B */

Nota: La sintaxis utilizada con f2 es la forma estndar C++ para especificar que una funcin no puede lanzar excepciones, y que salvo indicacin en contrario (4 ), tampoco las funciones que puedan ser invocadas desde ella. No obstante, los compiladores Borland C++ y MS Visual C++ disponen de otra posibilidad sintctica para el mismo propsito ( 4.4.1b).

3 Tenga en cuenta que las funciones con especificador de excepcin no son susceptibles de sustitucin inline ( 4.4.6b). Por ejemplo, la sentencia: inline void f1() throw(int) { ... } dara lugar a una advertencia del compilador: Warning: Functions with exception specifications are not expanded inline 4 Las excepciones sealadas para una funcin no afectan a otras funciones que pudieran ser llamadas durante su ejecucin. Por ejemplo:

func1() throw() { ... func2(); } func2() throw(A); ... try { ... func1 }

// func1 no puede lanzar excepciones // en esta zona no se lanzarn excepciones // func2 puede lanzar un objeto A

Durante la ejecucin del bloque de cdigo de func1 no pueden lanzarse excepciones de ningn tipo, pero si ocurren circunstancias adecuadas mientras se est ejecutando la invocacin a func2, desde esta s pueden lanzarse objetos tipo A. Todos los prototipos y definiciones de estas funciones deben tener un especificador de excepcin conteniendo la misma <lista-de-tipos>. Si una funcin lanza una excepcin cuyo tipo no est incluido en su especificacin, el programa llama a la funcin unexpected ( 1.6.3Excepciones imprevistas).

5 El sufijo no es parte del tipo de la funcin; en consecuencia, un puntero a funcin no se ver afectado por el especificador de excepcin que pueda tener la funcin. Este tipo de punteros solo comprueba el tipo de valor devuelto y los argumentos ( 4.2.4a). Por consiguiente, lo siguiente es legal: void void void fptr fptr f2(void) throw(); f3(void) throw(BETA); (* fptr)(); // Puntero a funcin devolviendo void = f2; // fptr se puede asignar a cualquiera = f3; // de las funciones f2 y f3

6 Hay que prestar atencin cuando se sobrecontrolan funciones virtuales, porque la especificacin de excepcin no se considera parte del tipo de funcin y existe el riesgo de violaciones en el diseo del programa.

7 Ejemplo 1 En el siguiente ejemplo la definicin de la clase derivada BETA::vfunc se hace de forma que no puede lanzar ninguna excepcin; se trata de una variacin de la definicin original en la clase base. class ALPHA { public: struct ALPHA_ERR {}; virtual void vfunc(void) throw (ALPHA_ERR) {} // Especificador de excepcin }; class BETA : public ALPHA { void vfunc(void) throw() {} // Se cambia el especificador de excepcin };

8 Ejemplo 3 Este ejemplo especifica que excepciones pueden lanzar las funciones festival y test. Ninguna otra excepcin podr ser lanzadas desde ambas. #include <stdio.h> bool pass; class Out{}; // festival solo puede lanzar excepciones Out void festival(bool firsttime) throw(Out) { if(firsttime) throw Out(); } void test() throw() { // test no puede lanzar ninguna excepcin try { festival(true); } catch(Out& e){ pass = true; } } int main() { pass = false; test(); return pass ? (puts("Excepcin manejada por test"),0) : (puts("Sin excepcin!!") ,1); } Salida: Excepcin manejada por test Si festival generase una excepcin distinta de Out, se considerara una excepcin imprevista, y el control del programa sera transferido a la funcin prevista para estos casos (ver al respecto el ejemplo siguiente). 9 Ejemplo 4 Se muestra que test no puede lanzar ninguna excepcin. Si alguna funcin (por ejemplo el operador new) en el cuerpo de test lanza una excepcin, la excepcin debe ser capturada y manejada dentro del propio cuerpo de test. En caso contrario, la excepcin representara una violacin de la especificacin de no-excepciones establecida para dicha funcin. Es posible establecer que set_unexpected() acepte un manejador diferente, pero en cualquier caso, ser invocada la funcin que se haya previsto para estos casos. #include <except.h> #include <process.h> #include <stdio.h> bool pass; class Out{}; void imprevisto(){ puts("*** Fallo ***"); exit(1); } void festival(bool firsttime) throw(Out) { // festival solo puede lanzar if(firsttime) throw Out(); // excepciones Out } void test() throw() { // test no puede lanzar ninguna excepcin try { festival(true); } catch(Out& e){ pass = true; throw; } // Error: intenta ralanzar Out }

int main() { // ============ set_unexpected(imprevisto); pass = false; test(); return pass ? (puts("Excepcin manejada por test"),0) : (puts("Sin excepcin !!") ,1); } Salida: *** Fallo ***

1.6.5 Excepciones en la prctica


1 Sinopsis Como se ha sealado anteriormente, en nuestras aplicaciones podemos prever la utilizacin de excepciones que lancen cualquier tipo de objeto. Por ejemplo, un int: int exception1 = 1; int exception2 = 2; ... try { ... if ( /* cond-1 */ ) throw exception1; if ( /* cond-2 */ ) throw exception2; ... } catch (int e) { if (e = 1) { /* action 1 */ } else if (e = 2) { /* action 2 */ } }

Sin embargo, salvo aplicaciones muy bsicas, casi diramos de experimentacin, en la prctica estaremos usando libreras que a su vez lancen excepciones. De modo, que lo mejor es utilizar objetos de una clase definida por nosotros que derive de la excepcin estndar, de la que a su vez podemos derivar tantas clases como aconsejen las circunstancias. De esta forma, podemos utilizar las mismas rutinas de captura para manejar nuestras excepciones y las que sean provocadas por las libreras. Por ejemplo, supongamos que estamos programando las rutinas de comunicaciones de una aplicacin Windows en la que empleamos las libreras Winsock, muchas de cuyas funciones lanzan excepciones en caso de error o circunstancias anmalas -por cierto muy frecuentes en las comunicaciones TCP/IP-. En este caso, para nuestras rutinas de comunicacin podramos utilizar una clase con el siguiente diseo: class TCPexception : public std::exception { std::string message;

public: TCPexception () : message("Error en conexion TCP/IP") {} TCPexception (const std::string& str) : message("Error en conexion TCP/IP:\n" + str) {} virtual const char* what() const throw() { return message.c_str(); } };

Como puede verse, la clase incluye un constructor explcito sin argumentos que construye un mensaje estndar y otro con un parmetro que permite aadir una aclaracin adicional segn el caso. Observe que el mtodo what() que nos permitir interrogar el mensaje del objeto capturado, goza de las siguientes caractersticas: Es virtual -> pueden existir otras definiciones en clases derivadas Devuelve un puntero a cadena de caracteres constante. No puede lanzar excepciones No puede modificar ninguna propiedad en la clase.

Con este diseo, podemos lanzar excepciones cuando las circunstancias lo exijan. Por ejemplo: if ( /* condition-1 */ ) throw TCPexception(); else if ( /* condition-2 */ ) throw TCPexception("Connection closed by remote peer");

En estas circunstancias, nuestras operaciones de comunicacin podran estar controladas de la siguiente forma [1]: try { /* rutinas de comunicacion */ } catch (std::exception& e) { // capturador-1 std::cerr << e.what(); } catch(...) { // capturador genrico std::cerr << "Unhandled exception"; }

Segn se indic al tratar de las reglas de concordancia para la captura de excepciones ( 1.6.2), un diseo como el presentado tiene la ventaja de que el capturador-1 permitir la captura de nuestras excepciones junto con las posibles excepciones estndar que pudieran ser lanzadas por las libreras utilizadas -si estn bien diseadas, probablemente sus excepciones sean tambin derivadas de las excepciones estndar-, mientras que el capturador genrico que sigue, permitira capturar todas las dems.

Respecto a la discriminacin entre nuestras ecepciones y las que pudieran ser lanzadas por la librera, no existir ningn problema al respecto segn qued demostrado en el ejemplo-3 de la pgina anterior ( 1.6.2).

1.7 Programacin actual


"However, pease remember that the aims are simplicity, regularity, and performance. You don't prove that you are clever by producing the most complicated code". Bjarne Stroustrup en "The State of the Language".

1 Introduccin Loque podamos llamar "programacin tradicional", por ejemplo la que se utilizaba (utiliza?) en la confeccin de programas para los primitivos PCs bajo MS-DOS, o en los actuales Win-32 bajo una "ventana" DOS, es un concepto un tanto ambiguo, pero podemos intentar una definicin diciendo que se basa en algunas premisas y caractersticas bastante definidas. Estas caractersticas pueden coexistir juntas o faltar alguna, pero en general se dan simultneamente. En este captulo intentaremos mostrar una visin sinptica de las diferencias entre esta y la programacin "moderna" [1], a la que seguramente tendr que adaptarse el programador C++. Sin embargo, advertiremos desde ahora que la mentada "programacin tradicional" mezcla conceptos distintos e independientes, cuyas diferencias es importante tener claras antes de adentrarnos en la programacin actual. Por ejemplo, la programacin para Windows-32; asunto este que desde luego va ms all que la simple utilizacin de "objetos". Este captulo se ha redactado teniendo en mente la multitud de problemas con que se enfrenta el programador "tradicional" que se ve abocado a trabajar para un SO moderno (Windows inevitablemente?). Intentaremos dentro de lo posible clarificar algunas ideas de ese cmulo de nuevos conceptos que con demasiada frecuencia producen ms de una "indigestin" inicial [2].

2 Sinopsis Para situarse correctamente en el asunto, es fundamental entender que las diferencias entre la programacin tradicional y la actual tienen su origen inmediato en la evolucin de los Sistemas Operativos, evolucin que ha sido posible a su vez por la evolucin del hardware. Por ejemplo, mquinas capaces de direccionar ms memoria. Tambin por la evolucin de las herramientas de programacin (lenguajes). Las caractersticas y diferencias de la programacin tradicional frente a la actual pueden resumirse en el siguiente cuadro:

Programacin tradicional Representacin en modo texto Trabajo en mono-programa Trabajo en mono-tarea

Programacin actual Representacin en modo grfico

Ejecucin controlada por el programa Programacin de tipos fijos

Trabajo en multi programacin Trabajo en multi-tarea Ejecucin controlada por el Sistema Programacin orientada a objetos

3 Caractersticas de la "Programacin Tradicional" 3.1 Representacin en modo texto Se trabaja en un entorno de texto (no grfico), el programa en ejecucin controla la informacin representada en la totalidad de la pantalla (no hay "ventanas"); el control de esta se realiza en trmino de filas y columnas (generalmente 24 x 80) y un surtido muy limitado de 256 caracteres (ASCII char set 2.2.1a). Los nicos atributos que pueden tener los caracteres suelen ser: Color de tinta y de papel (trazo y fondo); subrayado y parpadeo. Cuando se trabaja en entornos grficos, este tipo de aplicaciones no-grficas se denominan "de Consola". En la nomenclatura Windows a este tipo de aplicaciones se las conoce como CUI ("Console User Interface") en contraposicin con las de interfaz grfica GUI ("Graphical User Interface") que comentamos ms adelante ( 4.1) .

3.2 Trabajo en mono-programa El SO. no admite multiprogramacin, es decir, solo corre una aplicacin cada vez. Cuando ejecutamos nuestro programa no tiene que compartir recursos con ningn otro (por ejemplo, nuestras rdenes de impresin pueden ser dirigidas directamente al "puerto" de impresora). Esto hace que en general podamos utilizar rutinas y llamadas de "bajo nivel" sin peligro alguno de interferir con nada. La totalidad de los recursos, tales como el procesador, la memoria, los puertos E/S etc. estn a nuestra disposicin exclusiva.

3.3 Trabajo en mono-tarea El programa solo tiene una va, hilo o hebra ("Thread") de ejecucin, decimos que es mono-hebra. Coloquialmente podemos decir que solo hace una cosa cada vez.

3.4 Ejecucin controlada por el programa Desde su concepcin, el programador decide que ocurre exactamente en cada momento de la ejecucin del programa, de forma que las vas de actuacin pueden ser previstas de antemano.

Desde un punto de vista funcional, esto significa que, por ejemplo, se puede decidir en que momento se atender una llamada del teclado o en que momento atender la UART del puerto serie para atender la llegada o envo de datos. Desde el punto de vista del cdigo, el programa (suponemos que es C) se inicia en la funcin main y termina cuando esta termina. main puede llamar a otras funciones (que pueden llamar a su vez a otras funciones), pero siempre es el programa el que decide cual es la va de accin en cada caso; que funciones se invocan y cuando. En el fondo, esta caracterstica es consecuencia de que el programa controla ms o menos directamente sus propios procesos de entrada/salida. Puede por ejemplo "leer" el teclado o "escribir" directamente en el puerto de impresora, por lo que las decisiones pueden tomarse "desde dentro" del programa.

3.5 Programacin "procedural" Aqu utilizamos el trmino "procedural" [3] para indicar que generalmente no se utilizan lenguajes orientados a objetos (POO). Los lenguajes empleados adoptan una fuerte compartimentacin entre los tipos de datos disponibles y las operaciones posibles con ellos, que son fijas e incluidas en el propio lenguaje. No existen las clases como nuevos tipos que encapsulan el dato y sus operaciones ( 1.1), las entidades de mayor nivel de abstraccin que pueden utilizarse son las estructuras o uniones del C clsico y similares.

4 Caractersticas de la Programacin "Moderna" Debemos recalcar de nuevo que se trata de conceptos generales e independientes. Por ejemplo, un programa "moderno" puede ser multiprogramacin pero en modo texto, o no orientado a objetos; sin embargo, la mayora de las caracterstica se dan juntas. En especial si se trata de programas que utilizan la interfaz grfica de los SOs ms conocidos.

4.1 Representacin en modo grfico El usuario dispone de una interfaz grfica GUI ("Graphical User Interface") para trabajar en la aplicacin. Este tipo de programas controlan la informacin representada en su "canvas" [7], un trozo (ventana) de la pantalla cuyo tamao puede controlar el usuario la mayora de las veces. La representacin se realiza en pixels [8], y se dispone de un amplsimo surtido de herramientas y parmetros de representacin; no solo un amplio juego caracteres ("Fonts") con todos sus atributos, tambin un pincel ("Brush"), una pluma ("Pen"), as como iconos e imgenes preconstruidas de todo tipo. La posibilidad de multiprograma reseada a continuacin , junto con las nuevas posibilidades grficas, hacen que la pantalla pueda contener mltiples "ventanas", representativas de otros tantos procesos en ejecucin. El resultado es lo que se ha dado en llamar "Metfora de un escritorio" ("Desktop metaphor"). A su vez la pantalla se convierte en otro dispositivo de entrada (no solo el teclado); puede arrastrarse, cortarse y copiarse informacin de un punto a otro. Incluso entre aplicaciones diferentes. Ni que decir tiene que la programacin de un entorno grfico es muchsimo ms compleja que para un entorno de texto, aunque afortunadamente los entornos de desarrollo actuales ofrecen multitud de soluciones preconstruidas (clases) y libreras que facilitan la labor, de forma que si un programador quiere insertar, por ejemplo, un botn en un formulario, no tiene que preocuparse en

"dibujar" el botn y su posible etiqueta. Simplemente indicar su tamao; el texto o dibujo de su etiqueta; su posicin en el "canvas", y las acciones a tomar cuando en dicho objeto se produzcan determinados "eventos": que pase por encima el cursor; que reciba foco; que se haga clic sobre l, etc.). Desde el punto de vista de la interfaz que percibe el usuario, existen dos tipos de aplicaciones: SDI y MDI. SDI significa "Single Document interface". Son aplicaciones que se desarrollan en una sola ventana. Por su parte, MDI ("Multiple Document Interface") supone que la interfaz de la aplicacin es una ventana maestra ("Frame window"), que puede contener mltiples ventanas hijas o "documentos" abiertos simultneamente. Estas ventanas descendientes coinciden con la ventana madre cuando son maximizadas, y se cierran cuando se termina el proceso principal (que abri la ventana maestra).

4.2 Trabajo en multi-programa El SO admite multiprogramacin ("multiprogramming") es decir, se ejecutan mltiples aplicaciones a la vez. De hecho, incluso en un sencillo ordenador personal, el propio Sistema puede mantener en ejecucin simultanea seis u ocho aplicaciones para s mismo, adems de los programas de aplicacin del usuario. Esta operacin se ejecuta de forma preemptiva. Lo que significa que el SO tiene un control continuo sobre el procesador y los diversos programas en ejecucin. Por ejemplo, el SO puede abortar, suspender o asignar tiempos de ejecucin a cualquiera de ellos. Nota: La ejecucin no preemptiva se denomina cooperativa, y es propia de sistemas multiprograma antiguos. El control es copado por el programa en ejecucin, que debe terminar por s mismo para que pueda ejecutarse otra aplicacin. En estas condiciones, si una aplicacin no termina por s misma puede bloquear el Sistema, razn por la que se necesita la "cooperacin" de la aplicacin para que el Sistema funcione correctamente [9].

El usuario puede estar ejecutando diversos programas simultneamente, siendo muy fcil saltar de uno a otro. Por ejemplo, puede estar escribiendo un documento con un procesador de texto y simultneamente, estar consultando ciertos datos que necesita en el Navegador de Internet o en una hoja de clculo. Incluso puede estar ejecutando al mismo tiempo diversas activaciones de un mismo programa. En estas condiciones, cuando ejecutamos nuestro programa, debe compartir recursos con todas las dems aplicaciones en ejecucin. Por ejemplo, nuestras rdenes de impresin no estn ya directamente dirigidas al "puerto" de impresora; en cambio se "lanza" una tarea a un programa (monitor de tareas) que recibe la peticin y la encola en un proceso batch que es el que en realidad gobierna la impresin. Esto hace que en general no sea ya posible utilizar rutinas y operaciones E/S de "bajo nivel", pues estos asuntos son controlados directamente por el Sistema Operativo, todo lo ms que se puede hacer es realizar "peticiones" al sistema de determinados servicios siguiendo las convenciones (a veces muy complejas) establecidas en cada caso. Tenga en cuenta que por ejemplo, en un programa Windows, los datos de la propia ventana en la que corre el programa estn en una zona de memoria gobernada por el Sistema, y que esta informacin queda oculta al programa, que debe limitarse a manejar los mensajes que le llegan desde aquel. Nota: En el captulo dedicado al SO Windows ( 0.7) puede verse un punto de vista complementario, como maneja este Sistema las aplicaciones que corren en l.

La totalidad de los recursos, tales como el procesador, la memoria, los puertos E/S etc. no estn ya a nuestra disposicin. Es el SO el que los controla, procurando que nuestro programa no haga algo equivocado que pueda daar el resto de las aplicaciones. Cuando por error un programa intenta salir del mbito que se le ha asignado (por ejemplo escribiendo en una zona de memoria equivocada), es el SO el que nos alerta. Puede tratarse del clsico mensajito: Este programa ha realizado una operacin no vlida y ser interrumpido. Si el problema persiste consulte con el proveedor del programa (Horror, si soy yo mismo...). Como puede suponerse, las implicaciones para el programador que trabaja sobre uno de estos entornos son enormes, puesto que adems del conocimiento de un lenguaje de programacin adecuado (C++ por ejemplo), se exige el conocimiento de la interfaz; lo que se denomina la API(Application Programmer Interface) del sistema ( 1.7.1). El resultado de que sea el propio SO el que controla las E/S del programa, es que el funcionamiento de este es en realidad un dilogo continuo con el Sistema, del que recibe determinada informacin (entradas), y del que solicita determinados servicios (salidas).

4.3 Trabajo en multi-tarea: El programa puede tener ms de una va, hilo o hebra (thread) de ejecucin secuencial; es multihebra ("multithread"). Coloquialmente decimos que puede hacer varias cosas al mismo tiempo. Esto significa que el programa puede recorrer diversas vas de ejecucin simultanea (a cada una de estas vas o caminos de ejecucin lo denominamos una "tarea"). El programador debe controlar dos o ms vas de ejecucin paralela que pueden estar o no sincronizadas entre si ( 1.7.2). En rigor solo los equipos multiprocesador son capaces de realizar una autntica multitarea; los dotados de un solo procesador son capaces de realizar una suerte de simulacin de tiempo compartido, siempre que el SO y el lenguaje utilizado estn habilitados para ello, aunque desde el punto de vista de la lgica de la aplicacin este detalle sea inapreciable. Aunque el hardware sea adecuado, no todos los Sistemas Operativos son capaces de soportar este tipo de ejecucin. Por ejemplo, Windows 95 solo simula la multitarea, incluso si el hardware es adecuado. Ver algunos comentarios a estos conceptos: Multiprograma & multitarea ( 1.7b).

4.4 Control de ejecucin orientado a "Eventos" Aparte de los otros tpicos que se mencionan en este captulo, la forma en que se controla la ejecucin de un programa para un entorno "moderno" (tipo Windows-32) es bastante distinta de la utilizada en los entornos "clsicos". Este cambio es el que suele provocar mayor desconcierto inicial, y sobre el que quizs encuentre menos informacin (existen muchos libros sobre Programacin Orientada a Objetos y comparativamente pocos sobre Programacin Orientada a Eventos). Es en mi opinin, la adaptacin mental ms trabajosa. El programador clsico se ve obligado a "cambiar el chip" por otro radicalmente distinto. En contra de lo que ocurre en la programacin "tradicional", en la que es el propio programa el que decide que se hace y cuando, en la programacin orientada a eventos la ejecucin est controlada por el SO. El programa se parece a un bucle que espera continuamente la recepcin de mensajes del Sistema y responde en consecuencia ejecutando determinados procesos. Los procesos pueden incluir peticiones de nueva informacin, o solicitud de determinados servicios.

Los mensajes del Sistema tienen su origen en eventos (sucesos) de etiologa muy distinta. Por ejemplo, una interrupcin del teclado; del ratn, que puede hacer clic o doble clic en cualquier rea de la ventana de la aplicacin, o simplemente pasar sobre una zona determinada. Incluso la terminacin del programa ocurre cuando se recibe una peticin del sistema en este sentido (porque se ha hecho clic en el botn de cerrar la aplicacin, o por cualquiera de los otros procedimientos tpicos en las aplicaciones grficas). Ver algunas matizaciones a estos conceptos en "Controlar un programa" ( 1.7a).

4.5 Programacin Orientada a Objetos Los lenguajes empleados utilizan los recursos de esta tcnica de programacin ( 1.1). Las principales ventajas, aparte de un mejor encapsulamiento de los datos y sus operaciones, son la herencia; la sobrecarga, y el polimorfismo. Virtualmente no existe lmite a la complejidad de los "nuevos" tipos de datos que pueda crear el programador, ni de sus operaciones. Aparte de las ventajas genricas antes enunciadas, la POO est especialmente indicada para la programacin en los nuevos entornos operativos, porque los nuevos paradigmas de programacin para entornos distribuidos (Redes) conciben las aplicaciones, y sus relaciones con el mundo exterior, como un mundo de objetos que dialogan y transaccionan ( 1.7.1). Un mundo del que solo es necesario conocer las reglas de dilogo y transaccin, y este es justamente uno de los paradigmas de la POO.

1.7.1 Tecnicismos - I 1 Presentacin Se incluyen en este captulo una serie de conceptos no directamente relacionados con el lenguaje C++, aunque si desde luego con la Informtica, y que ms pronto que despus, tendr que manejar cualquier programador. Se trata de un cajn de sastre que incluye conceptos y definiciones que en su momento me hubiera gustado encontrar explicitadas en algn sitio, y que con demasiada frecuencia se dan "por sabidas", cuando en realidad algunas son de cultura general informtica, pero de tal naturaleza que quizs no estn incluidas en ninguna asignatura, de forma que las ms de las veces el futuro informtico tiene que aprenderlas por su cuenta. As pues, pido perdn a los "puristas"; a los que opinen que estos contenidos son impropios de un "Manual de C++" (s, un da de estos cambiar el ttulo), pues sencillamente... pueden pasar la pgina.

2 Unidades de medida Permitidme introducir un cuadro recordatorio de los mltiplos y divisores de las unidades de medida; muchas de ellas son de uso frecuentsimo en informtica y en ocasiones no recordamos que significan exactamente.

Yx Zx

Yota Zeta

1024 = 1.000...(24 ceros)...000 1021 = 1.000...(21 ceros)...000

Milln de trillones Mil trillones

Ex Px Tx Gx

Exa Peta Tera Giga

1018 = 1.000.000.000.000.000.000
1015 = 1.000.000.000.000.000 1012 = 1.000.000.000.000 109 = 1.000.000.000

Trilln espaol (un milln de Teras) [8]. Mil billones (espaol), un milln de Gigas Milln de millones; Billn espaol, Trilln USA [5]. Mil Millones (mil Megas); el Billn USA. Ej. 1 GB. [12] Milln. Ejemplo: 1 MB = 1 Megabyte

Mx Mega 106 = 1.000.000 Mx Miria 104 = 10.000 Kx Hx Dx x dx cx Kilo 103 = 1.000

No usado en informtica Millar (mil). Ejemplo: 1 KB = 1 Kilobyte

Hecto 102 = 100 Deca 101 = 10

No usado en informtica No usado en informtica Unidad dcima centsima milsima. millonsima. Ejemplo: 1 ms = milisegundo Ejemplo: 1 F = microfaradio Ejemplo 1 B = Byte; 1 b = bit

unidad 100 = 1 deci centi 10-1 = 0.1 10-2 = 0.01 10-3 = 0.001

mx mili x nx px fx ax zx yx

micro 10-6 = 0.000.001 nano pico 10-9 = 0.000.000.001 10-12 = 0.000.000.000.001

milmillonsima. Ejemplo: 1 ns = nanosegundo billonsima.


milbillonsima. trillonsima miltrillonsima

Ejemplo: 1 pF = picofaradio

femto 10-15 = 0.000.000.000.000.001 atto 10-18 = 0.000.000.000.000.000.001

zepto 10-21 = 0.000...(20 ceros)...001 atto 10-24 = 0.000...(23 ceros)...001

Nota-1: El micro se identifica con la letra griega ". Pero puede sustituirse por una "u" si no se dispone de dicho alfabeto. Ejemplo: 1 uF = microfaradio. En la literatura especializada es frecuente leer expresiones como "micrones" o "micras" para referirse a este divisor (lo correcto sera decir micrmetros). Por ejemplo, podemos leer: "La nueva tecnologa de conductores de

cobre en los procesadores permite rebajar a 0.13 el tamao de los de los actuales conductores de aluminio de 0.18 micrones". Nota-2: Algunos tratados y libros de informtica se empean en ensearnos que 1KByte es igual a 1.024 Bytes, que 64 KB son 65.536 Bytes y que 1 MByte son 1.048.576 Bytes, lo que supongo causa gran consternacin entre las personas "normales". Estos autores se saltan las normas de urbanidad y buena conducta (para con el resto de los mortales) e inventan directamente su particular sistema de medidas [9]. Las definiciones utilizadas seran las siguientes:

Px Tx Gx

Peta 250 = 1.125.899.906.842.624 Tera 240 = 1.099.511.627.776 Giga 230 = 1.073.741.824

Mx Mega 220 = 1.048.576 Kx Kilo 210 = 1.024

Afortunadamente parece que las cosas volvern a su cauce. Aunque todava no es de general utilizacin, en Diciembre de 1998 la IEC [10] ha propuesto una estandarizacin para uso en el mundo digital (afortunadamente un Kilobit vuelve a tener 1000 bits):

Ei Pi Ti Gi Mi Ki

Exbi 260 = 1.152.921.504.606.846.976 Pebi 250 = 1.125.899.906.842.624 Tebi 240 = 1.099.511.627.776 Gibi 230 = 1.073.741.824 Mebi 220 = 1.048.576 Kibi 210 = 1.024

La razn de esta anormalidad del sistema mtrico decimal cuando se refiere a medidas "informticas", hay que buscarla en las consideraciones ya sealadas al tratar del almacenamiento interno de los Ordenadores Electrnicos Digitales ( 0.1). El hecho de que sean binarios sus dispositivos fsicos y su sistema de numeracin, hace que esta caracterstica se refleje en muchos n detalles, de forma que las potencias de dos (2 ) aparecen constantemente [6]. Por ejemplo, en la serie 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, ..., 32.768, 65.536, ..., 524.288, 1.048.576, ... lo ms prximo a 1K es 1.024, de forma que como en los sistemas reales la memoria crece en mdulos que son mltiplos de dos, lo ms "parecido" a 1KB son 1.024 Bytes (justamente de 1000 Bytes no encontraremos nada, no se fabrica).

Como complemento a lo anterior, a reproducimos el comentario de Raimond Chen, en su obra "The Old New Thing". Addison-Wesley [7]. In 2003, a lawsuit charging computer manufacturer of misleading consumers over hard drive capacity caused a momentary uproar. The manufacturers use the ISO definition, wherein a "gigabyte" is one billion bytes, even though most people consider a gigabyte to be 1024 megabytes. This is a tricky one. The computer industry is itself inconsistent as to whether the "kilo", "mega", etc. prefixes refer to powers of ten or powers of two. The only place you see powers of two is when describing storage capacity. Everything else is powers of ten: Your 1GHz processor is running at one billion cycles per second, not 1,073,741,824 cycles per second. Your 28.8K modem runs at 28,800 bytes per second, not 29,491. And your 19" monitor measures only 17.4" inches diagonally. (Okay, that last one was a joke, but it's another case where the quoted value in't ncessarily measured the way you expect.) IEC standard designations for power-of-two multipliers. A kibibyte (KiB) is 1024 bytes, a mebibyte (MiB) is 1024 KiB, and a gibibyte (GiB) is 1024 MiB. Good luck finding anybody who actually uses these terms. At least they don't report sized in terms of unformatted capacity any more.

2.1 Al objeto de proporcionar una escala conceptual de lo que significan estos mltiplos y divisores de la unidad, a continuacin mostramos una tabla con algunas magnitudes relacionadas con la unidad de longitud.

Factor Ud unidad 1024 Ym Yotmetro

Escala Escala csmica (el tamao del universo observable es de 10.000 Ym).

1015 1012

Pm Petmetro Tm Termetro Distancias planetarias (la distancia media de Saturno al Sol es de 1.4 Tm) Escala planetaria (la tierra tiene 0.127 Gm de dimetro, Saturno 2.398 Gm)

109

Gm Gigmetro

106 103 100 10-2

Mm Megmetro Escala de un pas (1.000 Km) Km Kilmetro m metro Escala de un pueblo Escala del hombre (1-2 m)

cm centmetro Un ratn (5 cm), microondas.

10-3 10-6

mm milmetro

Un insecto

m micrmetro Escala bacteriana (el tamao tpico de una bacteria est entre 1 y 10 m). Nanotecnologa nm nanmetro Escala molecular (una molcula de agua mide 1 nm). Longitud de onda de los rayos X. pm picmetro Escala atmica

10-9

10-12 10-15

fm femtmetro Escala subatmica (tamao del ncleo del tomo)

API de MS Windows
1 WinAPI La interfaz de programacin de los sistemas Windows de Microsoft se concreta en varios centenares de funciones C/C++, que pueden ser invocadas para acceder a los servicios ms variados, y que a veces aceptan gran cantidad de argumentos [0]. Estas funciones se conocen con el nombre genrico de WinAPI, ms concretamente Win16 si se refiere a los antiguos sistemas de 16 bits, y Win32 si se refiere a los Windows 95 y posteriores, que pueden utilizar direcciones de memoria de 32 bits [1]. La mayora de funciones de la API se agrupan en libreras que adoptan forma de ficheros; generalmente con terminacin .DLL (Dynamic Linked Library 1.4.4b), aunque las hay tambin tipo .EXE. Dato que Windows es fundamentalmente un sistema grfico, una parte fundamental de sus habilidades estn dedicadas al dibujo en dispositivos de este tipo. Bsicamente pantallas, aunque tambin pueden ser impresoras y trazadores ("plotters"). Este subsistema es conocido como GDI("Graphics Device Interface"). Nota: tngase en cuenta que, incluso cuando se "escribe" en una pantalla Windows, en realidad se estn "dibujando" los caracteres. De acuerdo con Charles Petzold en su clsico "Programming Windows", las estructuras de datos utilizadas en la GDI para definir los "fonts" de caracteres, estn entre las mayores de Windows.

2 Informacin Junto con Windows 3.0, Microsoft comenz a comercializar los denominados SDK ("Software Developper Kit"), un conjunto de herramientas, documentacin y ejemplos pensados para el programador de aplicaciones Windows. Los SDK incluyen toda la informacin que el programador puede necesitar sobre la API de estos Sistemas. Desde entonces se actualizan regularmente y se

han convertido en herramienta indispensable para los programadores de este tipo de aplicaciones, ya que constituyen la "referencia oficial". Nota: Hace tiempo que esta documentacin no se publica en papel, solo en formato digital; est disponible en CD, aunque tambin puede ser descargado desde la Web de Microsoft. A su vez, la SDK es parte de un sistema de recursos mucho ms amplio denominado MSDN ("Microsoft Developer Network"). Se trata de la principal fuente de informacin que Microsoft pone a disposicin de la comunidad de programadores y empresas que desarrollan aplicaciones para plataformas Wintel. Tanto si se es usuario habitual de productos y tecnologas MS, como si se est estudiando hacerlo, es el punto de contacto que MS pone a disposicin de esta comunidad para que est al da de todos los cambios que se estn produciendo en sus diferentes productos. Este programa es una especie de paraguas que incluye una serie de contenidos de diferente ndole. Se distribuye en dos canales: de una parte, las subscripciones a MSDN, con envos mensuales de todos los productos y tecnologas de la compaa. De otra, la formacin e informacin que hay disponible en la Web: Web espaola del fabricante: www.msdn.microsoft.es. Web en ingls: http://msdn.microsoft.com

Generalmente, toda la informacin necesaria sobre la API de Windows, junto con la informacin adicional que compone la SDK, est incluida en el sistema de ayuda de algunos productos de Microsoft. Por ejemplo, en el que acompaa a los compiladores C++ 5.0 y siguientes, que son parte integrante de la suite MS Visual C++ [3]. Recientemente, Microsoft ha publicado distintas versiones de su "Window Server Platform SDK" (parte de Visual Studio), que estn disponibles para su descarga libre desde la Web . A la fecha de revisar estas lneas, la ltima es la Windows Server 2003 R2 SDK - March 2006 Edition. Incluye toda la informacin y herramientas necesarias para programar en los sistemas Windows de 32 y 64 bits. Puede ejecutarse en prcticamente todas las versiones de Windows 2000; Windows Server 2003 y Windows XP (aunque no en Windows 98). Para compilar los ejemplos necesitar un compilador C++, para lo que no sirve Visual C++ 6.0, aunque quizs puede descargar la versin Express de MS Visual C++ 2005, que se anuncia como "a free, lightweight, easy-to-use, and easyto-learn tools for the hobbyist, novice, and student developer" . Nota: Debo advertir que no he probado este compilador. Tambin que, como viene siendo habitual en el fabricante, esta versin de C++ dispone de opciones que, al menos de momento, NO son estndar.

Como fuente de informacin sobre la API, particularmente utilizo un fichero de ayuda (win32.hlp) titulado Win32 Programmer's Reference. Aunque es algo antiguo (Copyright 1992 - 1996), contiene prcticamente la totalidad de la API de Windows 32, a excepcin de las ltimas implementaciones. Adems de las ventajas habituales de este tipo de ayudas (pestaas "Contenido", "ndice" y "Buscar") contiene tres botones adicionales de navegacin: "Quick info", con informacin sobre ficheros de cabecera, libreras y compatibilidad de la funcin. "Group", con informacin sobre funciones relacionadas, y "Overview", con informacin conceptual. Puede descargarse del sitio de Borland Borland C++ developer support

Nota: el fichero aparece como Borland C++ 5.02 Window Help patch (5.6 MB). En realidad se descarga el fichero BC52HLP1.ZIP que contiene el mencionado fichero de ayuda win32.hlp de 24.2 MB. Otra va de informacin didctica, pueden ser recursos de la Web (como el presente); tambin mediante libros que tratan del tema, de los que se han escrito muchos. Un clsico al respecto, de fama bien merecida, es "Programming Windows", de Charles Petzold (Microsoft Press), que a lo largo de varias ediciones explica en detalle y con mltiples ejemplos, las complejidades de la interfaz de estos sistemas operativos. Otra va de aproximacin al problema [2], especialmente indicada para programadores C++, es utilizar la denominada MFC (Microsoft Foundation Classes). Es una librera de clases C++ que acompaa regularmente al compilador Visual C++, y ofrece un cmodo soporte para todos los servicios que puede ofrecer el sistema. De hecho, los mtodos de esta librera de clases C++ recrean muy de cerca las funciones de la SDK tradicional. Naturalmente, toda la informacin correspondiente a esta coleccin de clases, est incluida junto al compilador VC++. Otra fuente de informacin sobre esta librera es la obra Windows with MFC" de Jeff Prosise (Microsoft Press). "Programming

El Shell
1 Sinopsis El trmino "Shell" tiene un significado bastante amplio en informtica. A veces se refiere a la interfaz del Sistema que permite al operador enviar rdenes mediante comandos; es la interfaz del denominado CLI ("Command Line Interpreter"); intrprete de lneas de comando. Aunque este nombre cambia de un SO a otro, se refiere al 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 todava es accesible mediante las opciones "Abrir una ventana MS-DOS" o en: Men de Inicio Ejecutar. Los sistemas Unix y Linux ofrecen diversas formas alternativas para esta interfaz. En otras ocasiones es sinnimo de interfaz de usuario en cualquier programa (no necesariamente la interfaz del intrprete de rdenes del Sistema Operativo). En un sentido an ms general, la palabra se refiere a cualquier dispositivo software que acepta una entrada que contiene una orden (puede ser de teclado o leda de un fichero "Script") y la ejecuta; la enva a la utilidad que debe ejecutarla, o controla su ejecucin.

Você também pode gostar