ERRORES COMUNES DEL LENGUAJE C UTILIZANDO DEV C++ Y

Anuncio
DEV C++
ERRORES COMUNES DEL LENGUAJE C UTILIZANDO
DEV C++ Y SUS
POSIBLES SOLUCIONES.
Aquí tenemos los tres errores mas frecuentes de los principiantes de C utilizando Dev-C++ las cuales son:
Error: debido a que GCC requiere que TODAS las líneas terminen con el carácter de
'línea nueva' (el generado por la tecla {ENTER})
Código:
"8:2 C:\Dev\User\C\Hello_world\HelloWorld.c [Warning] no newline at end of file"
Solución: la forma más sencilla de solucionar este error es colocando el cursor de texto al final del archivo de código
fuente y después de esto simplemente presionamos la tecla {ENTER}.
Error: No hay "pausa automática" cuando se ejecuta una aplicación de consola desde
Dev-C++.
Por ejemplo este programa:
Código:
#include <stdio.h>
int main(void)
{
printf("Hola, Mundo\n");
return 0;
}
Aparece y desaparece en un instante.
Esto se debe a que a diferencia de otros IDEs Dev-C++ no soporta la generación automatica de esta pausa. La única
solución es que los usuarios de este IDE hagan una peticion para que eventualmente se añada esta característica.
Una "solución a medias" pero que funciona es utilizar la función getchar en esta forma:
Código:
#include <stdio.h>
int main(void)
{
printf("Hola, Mundo\n");
getchar();
return 0;
}
Error: Dev-C++ no soporta (reconoce) las funciones como gotoxy y getch
Por ejemplo este programa:
Código:
#include <stdio.h>
#include <conio.h>
int main() {
gotoxy( 20, 10 );
printf( "Hola" );
getch();
return 0;
}
Al ser compilado genera este error:
Código:
"[Linker error] undefined reference to `gotoxy'"
Esto se debe a que esa biblioteca debe instalarse aparte y como un "dev-pack". Para esto se debe estar conectado a
Internet y en Dev-C++ se selecciona el menu "Tools" (Herramientas), "Check for updates/packages" (Verificar
actualizaciones/paquetes).
En ese cuadro de dialogo:
1) En "Select devpack server" seleccionamos como servidor a "devpacks.org Community Devpacks".
2) Le damos click al boton "Check for updates" ("Revisar si hay actualizaciones").
3) Seleccionamos el devpack "CONIO" y le damos click al boton "Download selected" (descargar seleccionados).
Después de esto aparece otro cuadro de dialogo donde debemos darle click al boton "Install" (Instalar).
Por ultimo debemos seleccionar el menu "Tools" (Herramientas), "Compiler options" (Opciones del compilador) y en el
cuadro de texto "Adds these commands to the linker command line". Después de esto el programa debe compilar
correctamente.
Revisemos otros errores de los muchísimos que hay.
Error: pausar programa
Los principiantes cuando ejecutan el programa no pueden ver el resultado. Para evitarlo se puede añadir antes de
return 0; la siguiente línea:
Código: system("PAUSE");
Si esto no funciona prueba a añadir getch();"
* Tomando en consideración que este es el primer programa que compila el lector y que:
A) system recurre a la librería de stdlib.h
B) getch recurre a la librería de conio.h
Seria mejor recomendar el uso de la función getchar ya que esta es la que requiere de menos cambios al codigo fuente:
Código:
#include <stdio.h>
int main() {
printf("Hola mundo\n");
getchar(); /* pausa */
return 0;
}
Error: Al definir la función Main
"int main()"
Esta es una forma correcta de definir a la funcion 'main' pero seria incluso mejor utilizar la forma "int
main(void)" ya que esta tambien es una forma correcta y algunos compiladores como Lcc-win32 y Pelles C
al ser utilizados en su modo mas estricto "se quejan" de la forma "int main()".
Error: Impresión de comillas
"Printf: Imprimir en pantalla":
Cita:
¿Qué pasa si queremos imprimir el símbolo Comillas ( " ) en pantalla? Por ejemplo imaginemos que
queremos escribir:
Código:
Esto es "extraño"
El uso de caracteres como las vocales acentuadas o, como en este caso la 'ñ' puede resultar problematicos ya que no
forman parte del código ASCII sino del "Extended ASCII character set".
Y debido a que los caracteres que componen este "character set" dependen de como este configurado el SO del lector
seria mejor evitar los caracteres que no formen parte del juego de caracteres ASCII.
Error: Impresión de
\
"Printf: Imprimir en pantalla":
"Esta contrabarra se usa en C para indicar al compilador que queremos meter símbolos especiales. Pero
¿Y si lo que queremos es usar '\' como un carácter normal y poner por ejemplo Hola\Adiós? Pues muy
fácil, volvemos a usar '\':
Código:
printf( "Hola\\Adiós" );
y esta doble '\' indica a C que lo que queremos es mostrar una '\'."
Seria bueno colocar después de este párrafo una lista de los caracteres especiales en el lenguaje C y que deben ir
precedidos por una '\':
Código:
\a alert
\b backspace
\f formfeed
\n newline
\r carriage return
\t horizontal tab
\v vertical tab
\\ backslash
\? question mark
\' single quote
\" double quote
alerta o campana
retroceso
---???
nueva linea
retorno de carro
tabulador horizontal
tabulador vertical
diagonal invertida
signo de interrogacion
comilla sencilla
comilla doble
Error: Retorno de Main
El valor de retorno de la funcion "main" seria mejor utilizar la forma:
Código:
int main(void)
{
/* ... */
}
Nota:
Hay una lista de las palabras reservadas o keywords, estas son según el estandar ISO-C90:
Código:
auto, double, int, struct, break, else, long, switch, case, enum, register, typedef, char, extern,
return, union, const, float, short, unsigned, continue, for, signed, void, default, goto, sizeof, volatile,
do, if, static, while, etc..
Error: Imprimir contenido de variables
Asignar valores a variables de tipo int, la primera llamada a la función printf tiene un error de sintaxis:
Código:
prinft( "El valor inicial de x es %i\n, x" );
Debería ser:
Código:
printf( "El valor inicial de x es %i\n", x );
Información importante.
1) En C los tipos utilizados para almacenar números se conocen como tipos aritméticos y se dividen en dos
grupos: tipos enteros y tipos de punto flotante.
2.1) Los tipos enteros son char, short int, int y long int. Los tipos short int y long int se pueden abreviar a solo
short y long.
2.2) Los indicadores de signo signed y unsigned solo pueden aplicarse a los tipos enteros. El primero indica que el tipo
puede almacenar tanto valores positivos como negativos y el segundo indica que solo se admiten valores no negativos,
esto es, solo se admite el cero y valores positivos.
2.3) Si se declara una variable de tipo short, int o long sin utilizar un indicador de signo esto es equivalente a utilizar el
indicador de signo signed. Por ejemplo:
signed int i;
int j;
“Declara dos variables de tipo signed int.”
2.4) La excepción es el tipo char. Cuando se declara una variable de tipo char sin utilizar un indicador de signo si esta
variable es equivalente a signed char o a unsigned char depende del compilador que estemos utilizando.
Por lo mismo si debemos tener total certeza de que nuestras variables de tipo char puedan almacenar (o no) valores
negativos es mejor indicarlo explícitamente utilizando ya sea signed char o unsigned char.
2.5) El numero de valores posibles y únicos que pueden almacenarse en un tipo entero depende del numero de bits que
lo componen y esta dado por la expresión 2^N donde N es el numero de bits. Por ejemplo si en nuestro caso el tipo
short esta compuesto por 16 bits el numero de valores posibles es 65,536.
2.6) En los tipos sin signo el valor mínimo que puede almacenarse es 0 y el máximo esta dado por 2^N - 1.
2.7) En los tipos con signo el valor mínimo esta dado por la expresión -(2^(N - 1) - 1) y el valor máximo por 2^(N - 1) - 1.
Por ejemplo si en nuestro caso el tipo signed char esta constituido por 8 bits entonces su valor mínimo es -127 y su
valor máximo 127.
2.8) El tipo short esta constituido por al menos 16 bits y el tipo long por al menos 32 bits. El tipo int "flota en medio" y
usualmente tiene el mismo numero de bits que el tipo short o el tipo long.
2.9) El numero de bits que componen a los tipos short, int y long siempre cumple esta regla:
short <= int <= long
2.10) El numero exacto de bits que componen a los tipos short, int y long depende de la implementación (es la
combinacion de software y hardware que estemos utilizando) que estemos utilizando.
2.11) El numero de bits por byte es usualmente 8 pero esto no esta garantizado, para conocer este valor podemos
utilizar la macro CHAR_BIT que esta definida en limits.h
2.12) Los tipos enteros sin signo no pueden desbordarse.
2.13) Los tipos enteros con signo si pueden desbordarse y esto produce "undefined behaviour". En español: si una
variable de tipo entero con signo se desborda cualquier cosa puede pasar: resultados incorrectos, el programa
simplemente revienta, etc.
3.1) Los tipos de punto flotante son float, double y long double.
3.2) La relacion entre los tipos float, double y long double es:
float <= double <= long double
3.3) Los rangos posibles de estos tres tipos depende de la implementación pero los tres están garantizados a poder
almacenar numeros en el rango -10^37 a 10^37.
Error: Enteros double
Un ejemplo es donde se manejan números de tipo double (un ejemplo es la constante “PI 3.1416”) seria mejor cambiar
el tipo de las variables del tipo int al tipo double.
Ejemplo:
Código:
#include <stdio.h>
int main(void) {
}
double radio;
double perimetro;
radio = 20.0;
perimetro = 2.0 * 3.1416 * radio;
printf("El perímetro es: %f\n", perimetro);
return 0;
Las constantes en base 10 (decimal) y sin fraccion ni exponente son de tipo signed int.
Algunos ejemplos:
Código:
a = 100;
b = 200;
c = 300;
d = 400;
NOTA: Para indicar de una forma explicita el tipo de una constante entera se pueden utilizar los sufijos 'U' y/o 'L' ya
sea en minusculas o mayusculas. El primero indica que es un tipo sin signo (unsigned) y el segundo que es de tipo 'long
int'.
Algunos ejemplos:
Código:
a = 100; /* 100 es de tipo signed int */
b = 200U; /* 200U es de tipo unsigned int */
c = 300L; /* 300L es de tipo signed long */
d = 400UL; /* 400UL es de tipo unsigned long */
¿Y que pasa si una constante "no cabe" en el tipo indicado?
Supongamos que (en nuestro caso) el valor maximo que se puede almacenar en el tipo int es 32767 y (por poner un
ejemplo) en un programa tenemos:
Código:
int a = 32768; /* 32768 "no cabe" en un int */
¿Que es lo que sucede?
Bueno, en realidad las constantes enteras *Y* en base 10 son del tipo mas pequeño que pueda almacenar el numero en
cuestion y el tipo de la constante se selecciona en este orden:
1) int
2) long
3) unsigned long
Debido a que en nuestro ejemplo 32768 no cabe en un int se revisa el tipo signed long y si en este tampoco cabe el tipo
de la constante es unsigned long y si la constante en cuestion tiene uno de los dos sufijos 'U' o 'L' el tipo a utilizar se
restringe (limite) y selecciona en este orden:
A) En el caso de utilizar 'U':
1) unsigned int
2) unsigned long
B) En el caso de utilizar 'L':
1) signed long
2) unsigned long
2) Las constantes en base 10 y con un punto decimal y/o exponente son de tipo double.
Algunos ejemplos:
Código:
a = 100.0; /* 100.0 es de tipo 'double' */
b = 10E2; /* 10E2 es de tipo 'double' */
Las constantes de punto flotante son de tipo double a menos que se utilize uno de estos sufijos ya sea en minusculas o
mayusculas:
A) El sufijo 'F' indica que la constante es de tipo float.
B) El sufijo 'L' indica que la constante es de tipo long double.
Solo se debe utilizar uno de estos sufijos pero no ambos.
Algunos ejemplos:
Código:
a = 100.0F /* 100.0F es de tipo float
*/
b = 200.0 /* 200.0 es de tipo double
*/
c = 300.0L /* 300.0L es de tipo long double */
Observacion:
En estos ultimos ejemplos y ejercicios no se utiliza la palabra reservada "return" para indicar el valor de retorno de la
funcion "main" lo que puede ocasionar que algunos compiladores al ser utilizados en su modo mas estricto posible emitan
una advertencia.
Por lo mismo seria mejor utilizar la forma:
Código:
/* ... */
int main(void) {
/* ... */
return 0;
}
Error : Errores de "Operadores lógicos" AND & OR.
1) Estos dos operadores garantizan el orden de evaluacion: primero se evalua el operando a su izquierda y solo despues
de esto (y si es necesario) el operando a su derecha.
2) A estos dos operadores se les conoce como "operadores de corto circuito" o "short circuit operators" debido a la
forma particular en la que operan:
* En el caso del operador AND su resultado es 0 si la primera expresion es falsa (igual a 0), la segunda expresion no se
evalua.
* En el caso del operador OR su resultado es 1 si la primera expresion es verdadera (diferente de 0), la segunda
expresion no se evalua.
Estos dos operadores son particularmente útiles cuando se debe evaluar (o no) una expresión dependiendo de la
evaluación de una expresión anterior.
Por ejemplo supongamos que tenemos dos números enteros (a y b) y tenemos que verificar si el primero (a) es un
multiplo del segundo (b).
Para esto solo tenemos que verificar si el modulo o residuo de la división de a entre b es igual a cero siempre y cuando
b, para empezar, no sea igual a cero.
Esto se puede implementar fácilmente con el operador lógico AND:
Código:
if ((b != 0) && (a % b == 0))
/* b es multiplo de a */
Aquí el operador AND primero evalúa la expresión a su izquierda y solo si esta es verdadera (b es diferente de cero)
se evalúa la expresión a su derecha (¿el residuo de a entre b es cero?).
Error: operadores sizeof
En los ejemplos de "Operadores Sizeof" se utiliza el indicador de tipo "%i" para imprimir el resultado del operador
sizeof cuando el resultado de este es de tipo size_t. Ahora bien, lo único que el estándar de C garantiza sobre este
tipo (size_t) es que es un "tipo entero sin signo".
Debido a esto lo mejor que se puede hacer en ISO-C90 para imprimir un valor de este tipo es convertirlo a unsigned
long e imprimirlo como tal, por ejemplo:
Código:
#include <stdio.h>
int main(void) {
printf( "Las variables tipo int ocupan %lu bytes\n",
(unsigned long) sizeof(int) );
return 0;
}
La mayoría de los programas y ejercicios utilizan letras acentuadas y esto debería evitarse ya que el carácter que se
presente en pantalla no esta garantizado. Bien podría ser la vocal acentuado o tal vez no.
Recomendacion:
Si se utiliza con la funcion scanf el indicador de tipo "%i" para convertir el contenido de la entrada estandar en un
numero entero. Esto puede llevar a ciertos errores logicos debido a que este indicador acepta tanto numeros en base
10 como en base 8 (octal) y 16 (hexadecimal), Evitarlo para cometer errores
Por ejemplo si por accidente (o casualidad) se introduce el numero "0123" este no se interpreta (como es de esperarse)
como un numero decimal sino como un numero octal.
Código:
Introduce un numero: 0123
Has tecleado el numero: 83
Por todo esto me parece mejor utilizar el indicador de tipo "%d" ya que este solo acepta numeros en base 10.
Ahora bien cuando se utiliza la funcion printf para enviar el texto "Introduce ..." a la salida estandar. Debido a que
esta cadena no termina con el caracter '\n' no se puede tomar por garantizado que el texto se envie inmediatamente a
nueva linea (stdout).
Para estar seguros de que esto asi suceda debemos utilizar:
Código:
fflush(stdout);
Inmediatamente despues de la llamada a printf.
O quizás en este ejemplo e error puede ser que falte la directiva:
#include <conio.h>
Error: La variable "letra" utilizada para almacenar el valor de retorno de las funciones getch y getche se declara como
tipo "char" cuando deberia ser de tipo "int".
Error cunado se utiliza "If",
Cunado se utiliza "If",este se envía mediante la funcion printf una cadena a la salida estándar, esa cadena no esta
garantizada a presentarse antes de que se pida un numero en cuestion (mediante la funcion scanf). Para asegurarnos de
que esto suceda se debe vaciar el bufer de la salida estandar mediante la funcion fflush:
Código:
#include <stdio.h>
int main(void) {
int num;
printf("Introduce un número ");
fflush(stdout);
scanf("%i", &num);
if (num == 10){
printf("El número es correcto\n");
}
return 0; }
Este error se repite en varios de los programas de ejemplo de libros "If - Else" e "if else if".
Error lógico: considerando la explicacion "else if":
Código:
else if ( a>10 && a<100 )
Debería utilizar los operadores "mayor o igual que" (el operador '>=') y el operador menor o igual que (el operador '<='):
Código:
else if (a >= 10 && a <= 100)
Un error es que se debería indicar de una forma explicita que solo pueden utilizarse como condiciones las expresiones
cuyo resultado sea de algun tipo entero como char, int, long, etc., por ejemplo esta expresion no seria valida como una
condicion:
Código:
1.0 + 1.0
Ya que ambos operándos y el resultado de la expresión son de tipo 'double'.
Error lógico: if, else
En el segundo ejemplo de este capitulo el bloque if .. else en la funcion 'compara':
Código:
if (a > b)
printf("%i es mayor que %i\n" , a, b);
else
printf("%i es mayor que %i\n", b, a);
Debería ser:
Código:
if (a > b)
printf("%i es mayor que %i\n" , a, b);
else if (b > a)
printf("%i es mayor que %i\n", b, a);
else
printf("%i es igual que %i\n", a, b);
Error logico: No utilizar operadores lógicos.
Código:
scanf("%i %i", num1, num2);
Deberia ser:
Código:
scanf("%i %i", &num1, &num2);
Al llamar a la funcion printf el indicador de tipo "%p" se utiliza para imprimir un valor de tipo "void *".
Debido a esto cuando se llama a esa funcion para imprimir el valor de un puntero este debe ser convertido
al tipo "void *" mediante una conversion explicita o casting.
Error en Arrays
Seria bueno aclarar que estas listas solo pueden consistir de valores literales para no cometer errores, por poner un
ejemplo:
Código:
int array[3] = {1, 2, 3};
y constantes simbolicas definidas mediante la directiva "define":
Código:
#define ELEMENTOS 24
Código:
#include <stdio.h>
int main(void)
{
int hora;
int temperaturas[] = {15, 18, 20, 23, 22, 24, 22, 25, 26, 25, 24, 22, 21, 20, 18, 17, 16, 17, 15, 14};
El error seria que no se declare de cuantos elementos constara el arreglo.
Error en Arrays bidimensionales
Cuando se declara un array de arrays (mejor conocido como array bidimensional o matriz), por ejemplo:
Código:
int tabla[4][3];
Los elementos de este tipo de array pueden inicializarse de dos formas ligeramente distintas (y funcionales) utilizando
listas de valores.
La primera consiste en utilizar una sola lista de valores, siguiendo nuestro ejemplo:
Código:
int tabla[4][3] = {1, 2, 3, 11, 12, 13, 21, 22, 23};
y de esta manera se evitan muchos errores
o bien se puede hacer de la siguiente manera en donde la lista de valores se utiliza para inicializar todos los elementos
de la primera fila, después la segunda fila, después la tercera fila y así sucesivamente.
Código:
tabla[0][0] tiene el valor 1
tabla[0][1] tiene el valor 2
tabla[0][2] tiene el valor 3
...
tabla[2][2] tiene el valor 23
Otra forma de hacerlo para evitar errores seria: con una lista "doble":
Código:
int tabla[4][3] = { { 1, 2}, {11, 12}, {21, 22}, {31, 32} };
En este caso los valores de la primera lista se utilizan para inicializar la primera fila, los valores de la segunda lista se
utilizan para inicializar la segunda fila, y así sucesivamente.
Un array de caracteres como este:
Código:
char dummy[128];
Errores comunes:
Error muy comun: Almacena en una variable de un tipo "x" cuando deberia ser de otro tipo distinto.
Error mas lógico de "Arrays de cadenas" es que falta incluir el archivo de inclusion o header <stdlib.h>.
Cuando se realizan llamadas a la funcion scanf no se debe utilizar el operador "direccion de" (el '&') ya que al ser
arrays los mismos campos generan el puntero de forma automatica.
Error # 1: No liberar memoria asignada por SafeArrayGetElement() cuando utiliza variantes
Cuando utiliza SafeArrayGetElement(), asegúrese de liberar la memoria que se ha asignado a la copia del elemento de
matriz segura.
Es el trabajo del llamador al asignar memoria copiando esta función el elemento de datos safearray y por lo tanto,
también es el trabajo del llamador que libera esta memoria cuando esta memoria finaliza con ello.
SafeArrayGetElement() asignará la memoria para los datos en el Variant (tal con respecto a un BSTR) y es el trabajo
del llamador que libera todas las memorias asociadas a la variante. El enfoque mejor para ello es utilizar que funciona
VariantClear(). Es un ejemplo:
... VARIANT varMyVal; // Variant allocated on stack HRESULT hResult; long ix; VariantInit(&varMyVal); ix =
1; // If the element in the safe array fetched was a BSTR, then the // appropriate memory will be allocated
for it, on the heap hResult = SafeArrayGetElement(m_psa, &ix, &varMyVal); // This will free the memory for
the data element retrieved from // the Safe Array (say a BSTR) VariantClear(&varMyVal);
Error # 2: No llamar a Release en punteros de interfaz COM. cuando utiliza QueryInterface()
Si está utilizando componentes COM en proceso, asegúrese de que libera todos los punteros de interfaz. Si no hace
esto, puede dejar instancias de componentes COM no utilizados asignados en el espacio de memoria del proceso.
El código siguiente muestra el control adecuado de un puntero, pObj, obtenido vía QueryInterface() en un objeto
existente seleccionado por punk. El pObj- > <B> Release(); </B> disminuirán el recuento de referencia en el objeto pero
no se liberará el objeto en momento de memoria aún al contener punk una referencia a ello. Cuando > pUnk- <B> se llama
a Release(); </B>, el recuento ref irá a 0 y se liberará el objeto. > > Release(), </B> entonces el pUnk- <B> Release(); </B>
no liberarán el objeto y tendrá una pérdida de memoria en su proceso si omite el pObj- <B>.
... CMyAtlObj* pObj; if (!SUCCEEDED(pUnk->QueryInterface(CLSID_MyAtlObj, (void **)&pObj))) return
NULL; //Use pObj ... pObj->Release(); // Needed because we called QueryInterface. ...
Para componentes COM que se registran para ejecutarse con Microsoft Transaction Server, será escrita una entrada
por Microsoft Transaction Server en el registro de sucesos de aplicación de Windows NT cuando detecta que no se ha
liberado correctamente un componente alojado antes de apagar. La entrada del registro de eventos tendrá suceso de
4098 y una descripción de "Count de Referencia de Objeto Inesperado". Si tiene estas entradas en el Registro de
sucesos de NT y si está experimentando, se pierde memoria en aquellos paquetes MTS (procesos) entonces no es
probable que se liberen correctamente punteros de interfaz COM.
Errores de codificación
Errores de sintaxis: son errores en el programa fuente. Pueden deberse a palabras reservadas mal escritas,
expresiones erróneas o incompletas, variables que no existen, etc. Los errores de sintaxis se detectan en la fase de
compilación. El compilador, además de generar el código objeto, nos dará una lista de errores de sintaxis. De hecho nos
dará sólo una cosa o la otra, ya que si hay errores no es posible generar un código objeto.
Avisos: además de errores, el compilador puede dar también avisos (warnings). Los avisos son errores, pero no lo
suficientemente graves como para impedir la generación del código objeto. No obstante, es importante corregir estos
avisos, ya que el compilador tiene que decidir entre varias opciones, y sus decisiones no tienen por qué coincidir con lo
que nosotros pretendemos, se basan en las directivas que los creadores del compilador decidieron durante su creación.
Errores de enlazado: el programa enlazador también puede encontrar errores. Normalmente se refieren a funciones
que no están definidas en ninguno de los ficheros objetos ni en las librerías. Puede que hayamos olvidado incluir alguna
librería, o algún fichero objeto, o puede que hayamos olvidado definir alguna función o variable, o lo hayamos hecho mal.
Errores de ejecución: incluso después de obtener un fichero ejecutable, es posible que se produzcan errores. En el
caso de los errores de ejecución normalmente no obtendremos mensajes de error, sino que simplemente el programa
terminará bruscamente. Estos errores son más difíciles de detectar y corregir. Existen programas auxiliares para
buscar estos errores, son los llamados depuradores (debuggers). Estos programas permiten
detener la ejecución de nuestros programas, inspeccionar variables y ejecutar nuestro programa paso a paso. Esto
resulta útil para detectar excepciones, errores sutiles, y fallos que se presentan dependiendo de circunstancias
distintas.
Errores de diseño: finalmente los errores más difíciles de corregir y prevenir. Si nos hemos equivocado al diseñar
nuestro algoritmo, no habrá ningún programa que nos pueda ayudar a corregir los nuestros. Contra estos errores sólo
cabe practicar y pensar.
Nota: los compiladores clasifican los errores en dos tipos, dependiendo de lo serios que sean:
"Errores": son errores que impiden que el programa pueda ejecutarse, los programas con "errores" no pueden pasar de
la fase de compilación a la de enlazado, que es la fase en que se obtiene el programa ejecutable.
"Warnings": son errores de poca entidad, (según el compilador que, por supuesto, no tiene ni idea de lo que intentamos
hacer). Estos errores no impiden pasar a la fase de enlazado, y por lo tanto es posible ejecutarlos. Debes tener
cuidado si tu compilador de da una lista de "warnings", eso significa que has cometido algún error, en cualquier caso
repasa esta lista e intenta corregir los "warnings".
A menudo recibiremos errores del compilador cuando usemos constantes long long sin
añadir el sufijo LL, por ejemplo:
if(x == 16575476522787) cout << "Número long long" << endl;
Esta sentencia hará que el compilador emita un error ya que no puede usar un tamaño mayor sin una indicación explícita.
Las excepciones son en realidad errores durante la ejecución. Si uno de esos errores se produce y no implementamos el
manejo de excepciones, el programa sencillamente terminará abruptamente. Es muy probable que si hay ficheros
abiertos no se guarde el contenido de los buffers, ni se cierren, además ciertos objetos no serán destruidos, y se
producirán fugas de memoria.
En programas pequeños podemos prever las situaciones en que se pueden producir excepciones y evitarlos. Las
excepciones más habituales son las de peticiones de memoria fallidas. Veamos este ejemplo, en el que intentamos crear
un array de cien millones de enteros:
#include <iostream>
using namespace std;
int main() {
int *x = NULL;
int y = 100000000;
x = new int[y];
x[10] = 0;
cout << "Puntero: " << (void *) x << endl;
delete[] x;
cin.get();
return 0;
}
El sistema operativo se quejará, y el programa terminará en el momento que intentamos asignar un valor a un elemento
del array. Podemos intentar evitar este error, comprobando el valor del puntero después del "new":
#include <iostream>
using namespace std;
int main() {
int *x = 0;
int y = 100000000;
x = new int[y];
if(x) {
x[10] = 0;
cout << "Puntero: " << (void *) x << endl;
delete[] x;
} else {
cout << "Memoria insuficiente." << endl;
}
cin.get();
return 0;
}
Pero esto tampoco funcionará, ya que es al procesar la sentencia que contiene el operador "new" cuando se produce la
excepción. Sólo nos queda evitar peticiones de memoria que puedan fallar, pero eso no es previsible. Sin embargo, C++
proporciona un mecanismo más potente para detectar errores de ejecución. Las excepciones. Para ello disponemos de
tres palabras reservadas extra: try, catch y throw, veamos un ejemplo:
#include <iostream>
using namespace std;
int main() {
int *x;
int y = 100000000;
try {
x = new int[y];
x[0] = 10;
cout << "Puntero: " << (void *) x << endl;
delete[] x;
}
catch(std::bad_alloc&) {
cout << "Memoria insuficiente" << endl;
}
cin.get();
return 0;
}
La manipulación de excepciones consiste en transferir la ejecución del programa desde el punto donde se produce la
excepción a un manipulador que coincida con el motivo de la excepción. Como vemos en este ejemplo, un manipulador
consiste en un bloque "try", donde se incluye el código que puede producir la excepción. A continuación encontraremos
uno o varios manipuladores asociados al bloque "try", cada uno de esos manipuladores empiezan con la palabra "catch", y
entre paréntesis una referencia o un objeto.
En nuestro ejemplo se trata de una referencia a un objeto bad_alloc, que es el asociado a excepciones consecuencia de
aplicar el operador "new". También debe existir una expresión "throw", dentro del bloque "try". En nuestro caso es
implícita, ya que se trata de una excepción estándar, pero podría haber un "throw" explícito, por ejemplo:
#include <iostream>
using namespace std;
int main() {
try {
throw 'x'; // valor de tipo char
}
catch(char c) {
cout << "El valor de c es: " << c << endl;
}
catch(int n) {
cout << "El valor de n es: " << n << endl;
}
cin.get();
return 0;
}
El "throw" se comporta como un "return". Lo que sucede es lo siguiente: el valor devuelto por el "throw" se asigna al
objeto del "catch" adecuado. En este ejemplo, al tratarse de un carácter, se asigna a la variable 'c', en el "catch" que
contiene un parámetro de tipo "char". En el caso del operador "new", si se produce una excepción, se hace un "throw"
de un objeto de la clase "std::bad_alloc", y como no estamos interesados en ese objeto, sólo usamos el tipo, sin nombre.
El manipulador puede ser invocado por un "throw" que se encuentre dentro del bloque "try" asociado, o en una de las
funciones llamadas desde él.
Cuando se produce una excepción se busca un manipulador apropiado en el rango del "try" actual. Si no se encuentra se
retrocede al anterior, de modo recursivo, hasta encontrarlo. Cuando se encuentra se destruyen todos los objetos
locales en el nivel donde se ha localizado el manipulador, y en todos los niveles por los que hemos pasado.
#include <iostream>
using namespace std;
int main() {
try {
try {
try {
throw 'x'; // valor de tipo char
}
catch(int i) {}
catch(float k) {}
}
catch(unsigned int x) {}
}
catch(char c) {
cout << "El valor de c es: " << c << endl;
}
cin.get();
return 0;
}
En este ejemplo podemos comprobar que a pesar de haber hecho el "throw" en el tercer nivel del "try", el "catch" que
lo procesa es el del primer nivel. Los tipos de la expresión del "throw" y el especificado en el "catch" deben coincidir, o
bien, el tipo del catch debe ser una clase base de la expresión del "throw". La concordancia de tipos es muy estricta,
por ejemplo, no se considera como el mismo tipo "int" que "unsigned int".
Si no se encontrase ningún "catch" adecuado, se abandona el programa, del mismo modo que si se produce una
excepción y no hemos hecho ningún tipo de manipulación de excepciones. Los objetos locales no se destruyen, etc. Para
evitar eso existe un "catch" general, que captura cualquier "throw" para el que no exista un "catch":
#include <iostream>
using namespace std;
int main() {
try {
throw 'x'; //
}
catch(int c) {
cout << "El valor de c es: " << c << endl;
}
catch(...) {
cout << "Excepción imprevista" << endl;
}
cin.get();
return 0;
}
Descargar