Sunteți pe pagina 1din 9

Lo que no hay que hacer en C/C++

La mayora que recin est empezando con C/C++ y viene al foro a sacarse dudas, cometen errores
similares. Incluso muchos tienen conceptos errneos sobre la utilidad o eficiencia de ciertas
funciones, probablemente debido a el estudio de textos incorrectos o de libros anticuados.
Pero incluso a veces uno no puede escapar de ciertas cosas. Imaginen a alguien que recin entra en
la universidad y el profesor le hace incluir conio.h para utilizar getch() . Seguramente el alumno
de por sentado que hacer eso est perfecto, e incluso el profesor recompense con notas altas a los
que programan tal cual se ha enseado. Por lo tanto, ciertas cosas son comprensibles y el nico
consejo que se puede dar es que nunca den por sentado algo, es recomendable informarse antes para
ver qu es lo que se est realizando, sobre todo en programacin.
Para los que quieran revisar un poco explicaciones ms detalladas del "por qu" de ciertas reglas,
puede revisar este draft, que aunque puede tornarse bastante tcnico para alguien que recin
comienza, puede servir bastante:
WG14/N1256 Committee Draft Septermber 7, 2007 ISO/IEC 9899:TC3
(http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)
1) gets();
La falta de control en un programa es un factor bastante negativo, y tambin peligroso en caso de
programas comerciales o masivos. Ese es el principal problema de gets, no tiene control interno.
Que significa no tener control interno?
Significa que la funcin acarrea como consecuencia el mal comportamiento del programa, resultados
inesperados y errneos, vulnerabilidades entre otras tantas cosas. Define una cadena de 10
caracteres, y gets va a aceptarte eso y muchsimo mas como entrada.
Veamos este cdigo:
1. #include<stdio.h>
2.
3. int main()
4. {
5.
6. char letra1[]="AAAAA";
7. char letra2[]="BBBBB";
8. char letra3[]="CCCCC";
9.
10. printf("A: %s\nB: %s\nC: %s\n\n",letra1,letra2,letra3);
11.
12. printf("Ingrese la letra D, 5 veces: ");
13. gets(letra2);
14.

15. printf("\nA: %s\nB: %s\nC: %s\n\n",letra1,letra2,letra3);


16.
17. getchar();
18. }
A: AAAAA
B: BBBBB
C: CCCCC
Ingrese la letra D, 5 veces: DDDDD
A: AAAAA
B: DDDDD
C: CCCCC

Como vemos nada paso, y los elementos de letra2 se reemplazaron por el input ingresado como debera
ser. Ahora en vez de 5 veces, ingresemos la letra 'D' ms veces de lo que el programa nos indica:
A: AAAAA
B: BBBBB
C: CCCCC
Ingrese la letra D, 5 veces: DDDDDDDDDDDDDDDDDDD
A: DDD
B: DDDDD
C: CCCCC
Qu paso con letra1? precisamente lo que no queremos que pase en nuestro programa. gets
sobrescribi una zona de memoria que no deba sobrescribir, ya que el input deba ser ingresado en
letra2, pero simplemente no le importo que el input sea muchsimo ms grande que el tamao de la
cadena. Razn ms que suficiente para no utilizarla.
Por lo tanto es recomendable utilizar fgets();
1. fgets(char *string, int length, FILE * stream)

Es decir:
1. fgets(letra2, 5, stdin);

Con fgets permites que sean ingresados lenght-1 caracteres.


Aunque no todo es color de rosas, y en caso de que se ingresen menos caracteres de los que has
definido como tamao, tendrs que lidiar con un salto de lnea '\n'. Obviamente es una nimiedad que
puedes arreglarla de esta forma:
1. if (letra2[strlen(letra2)-1] == '\n')//string.h para strlen();
2. letra2[strlen(letra2)-1] = '\0';

Supongo que con esto queda claro que no tiene sentido utilizar gets();.
Links:
http://en.wikipedia.org/wiki/Fgets

http://www.gidnetwork.com/b-56.html
2) fflush(stdin);
fflush(stdin) es un invitado casi diario. Pocas veces pasa un da sin que alguien lo recomiende o
lo mencione como la solucin! a los malos comportamientos de las pausas en los programas.
STDIN, como su nombre lo indica, significa 'Standard input'. Es decir, el ingreso por teclado.
Acorde al Standard, fflush espera solamente un stream de salida (STDOUT: 'Standard Output) por lo
que el comportamiento con streams de entrada como STDIN es indefinido. Por ms que en algunas
plataformas funcione, o que en algunos compiladores funcione, no debera ser utilizado.
Por el otro lado, para evitar esas pausas fastidiosas es necesario evitar las funciones que dejan
basura por doquier (como scanf();) y utilizar funciones como la ya mencionada fgets();
http://foro.elhacker.net/programacion_cc/fflushstdin-t91101.0.html
http://foro.elhacker.net/programacion_cc/zanjar_de_una_vez_fflushstdin-t265125.0.html;msg1294511
http://foro.elhacker.net/programacion_cc/error_super_basico_en_c_quien_me_ayuda_soy_nuevot262118.0.html;msg1275898
http://foro.elhacker.net/programacion_cc/problema_con_el_compilador_gcc_de_ubuntu-t248582.0.html

3) system("PAUSE");
Otro invitado diario es la pausa utilizando system. Esto incluso me ha tocado en mi universidad. Que
el profesor me haga realizar ejercicios exclusivamente utilizando system y la pausa tambin haba
que realizarla exclusivamente utilizando system. En fin, seguramente muchos consideren que realizar
esto no es un gran problema, y probablemente tengan razn en los cdigos que uno realiza como tarea
estudiantil. Pero es un hbito malo, y como todo, hay que dejarlo algn da. Claramente no puedes
pasarte toda la vida llamando al sistema para hacer una pausa.
*No es portable, solo va a funcionar en sistemas que tengan el comando PAUSE.
*Es una funcin pesada. Llamar al sistema, suspender tu programa, buscar el comando PAUSE, etc. etc.
etc. etc.
Por el otro lado si se sigui la norma de no dejar basura en el buffer de entrada, se puede utilizar
soluciones ms sencillas como getchar(); (o cin.get(); en C++).
4) conio.h
Libreras intiles si las hay, pero por sobre todas las cosas NO standard. No existe problema para
el cual sea exclusivamente necesario utilizar conio.h. Y visto que ac la mayora de los que la
incluyen lo hacen para utilizar getch(); supongo que con todo el prrafo anterior habrn quedado
claras las alternativas.
http://en.wikipedia.org/wiki/Conio.h
http://www.gidforums.com/t-7379.html
5) void main();
Seguramente para justificar esto habra que decir que el estndar dice que void no puede ser el
valor de retorno del main, lo cual es verdad, pero no siempre es suficiente. Sobre todo considerando
que muchas veces en los libros el void main dice presente.
No voy a extenderme mucho con esto ya que voy a dejar los links necesarios para el que quiera
entender porque sucede, pero como base comprendamos esto:
-Devolver un valor void es perder la garanta que un valor de retorno distinto de 0 implica un
error, lo cual es malo tanto para el SO como para el determinado programa que pueda estar llamando a
tu cdigo.
-Est mal acorde al estndar.

-Puede que tu programa no funcione correctamente.


Los errores pueden depender de los mecanismos de retorno o la arquitectura especifica en la cual
estemos. Pero por supuesto, si elegimos un lenguaje de alto nivel como lo es C o C++, es para no
tener que preocuparnos por esos asuntos.
En conclusin, el que quiera entender el "por qu", leer los siguientes links.
Links:
http://www.eskimo.com/~scs/readings/voidmain.960823.html
http://home.att.net/~jackklein/ctips01.html#int_main
http://users.aber.ac.uk/auj/voidmain.shtml

6) Procesamiento de cadenas: string.h


La mayora de las funciones que no tienen la posibilidad de pasar como argumento el tamao del
buffer que debe procesarse, estn completamente expuestas al desbordamiento de buffer. Sobre todo,
si el contenido del buffer origen es ingresado por el usuario, y por sobre todas las cosas, si no se
hicieron las validaciones correspondientes. (Por ejemplo, haber utilizado gets o no haber chequeado
el tamao de la cadena antes de copiar/concatenar o cualquier otra forma de procesar una cadena).
Obviamente no voy a hablar sobre todas las funciones, pero si sobre dos ejemplos que dan la pauta de
lo que sucede y de lo que debera hacerse: strcpy y strcat.
a) strcpy ( char * destination, const char * source );
Veamos un ejemplo de un cdigo incorrecto:
1. #include <stdio.h>
2. #include <string.h>
3.
4. int main()
5. {
6. char cadena1[]={"aaaa"};
7. char cadena2[100];
8. char cadena3[]={"0123"};
9.
10. printf("Ingrese cadena: ");
11. fgets(cadena2,100,stdin);
12. printf("cadena1: %s\ncadena2: %s\ncadena3: %s\n\n",cadena1,cadena2,cadena3);

13. strcpy(cadena1,cadena2);
14. printf("cadena1: %s\ncadena2: %s\ncadena3: %s",cadena1,cadena2,cadena3);
15. getchar();
16. return 0;
17. }
Ingrese cadena: 1111111111111111111111111111111111111111111111111111
//Antes de strcpy
cadena1: aaaa
cadena2: 111111111111111111111111111111111111
cadena3: 0123
//Despus de strcpy
cadena1: 111111111111111111111111111111111111
cadena2: 111111111111111111111111111111111111
cadena3: 1111111111111111111111111111111111111111111111111111
Como se ve claramente, el problema esta vez no estuvo en la recepcin de los datos (en el sentido
tcnico), si no en el copiado. Ya que strcpy no verifica si el buffer destino es efectivamente capaz
de contener los datos del buffer origen, lo cual por supuesto ocasiona perdida de datos y buffers
overflows. Obviamente si el fgets se hubiese utilizado correctamente, esto no hubiese sucedido.

b) strcat ( char * destination, const char * source );


La misma historia sucede aqu. Al no poder especificar el tamao del buffer destino la funcin es
insegura. Veamos un ejemplo incorrecto:

1. #include <stdio.h>
2. #include <string.h>
3.
4. int main()
5. {
6. char cadena1[]={"aaaa"};
7. char cadena2[100];
8. char cadena3[]={"0123"};

9.
10. printf("Ingrese cadena: ");
11. fgets(cadena2,100,stdin);
12. printf("cadena1: %s\ncadena2: %s\ncadena3: %s\n\n",cadena1,cadena2,cadena3);
13. strcat(cadena1,cadena2);
14. printf("cadena1: %s\ncadena2: %s\ncadena3: %s",cadena1,cadena2,cadena3);
15. getchar();
16. return 0;
17. }
Ingrese cadena: 11111111111111111111111111111111111111111111
//Antes de strcat
cadena1: aaaa
cadena2: 1111111111111111111111111111
cadena3: 0123
//Despus de strcat
cadena1: aaaa1111111111111111111111111111
cadena2: 1111111111111111111111111111
cadena3: 11111111111111111111111111111111111111111111
Como pueden ver suceden errores muy similares. Obviamente es un error tonto de programacin en el
cual el desarrollador pasa un mal argumento a fgets, ya que de haberlo hecho bien el error no
sucedera. Pero ese no es el asunto, lo importante es que tanto strcpy o strcat (y toda la familia
de funciones) no realizan (o permiten realizar) la validacin extra necesaria. Imaginen que creas
una cadena para tomar una ruta del sistema operativo para luego concatenar, Y si el atacante se da
cuenta de eso y decide explotar el overflow por ese lado? mil variantes pueden existir y
probablemente no siempre uno cubra todas ellas.

c) Entonces que se puede utilizar?


strncpy y strncat cumplen su funcin perfectamente si se utilizan en forma correcta.
c1) strncpy ( char * destination, const char * source, size_t num );

1. #include <stdio.h>
2. #include <string.h>
3.
4. int main()
5. {
6. char cadena1[]={"aaaa"};
7. char cadena2[100];
8. char cadena3[]={"0123"};
9.
10. printf("Ingrese cadena: ");
11. fgets(cadena2,100,stdin);
12. printf("cadena1: %s\ncadena2: %s\ncadena3: %s\n\n",cadena1,cadena2,cadena3);
13. strncpy(cadena1,cadena2,1);
14. printf("cadena1: %s\ncadena2: %s\ncadena3: %s",cadena1,cadena2,cadena3);
15. getchar();
16. return 0;
17. }
Ingrese cadena: 1111111111111111111
//Antes de strncpy
cadena1: aaaa
cadena2: 1111111111111111111
cadena3: 0123
//Despus de strncpy
cadena1: 1aaa
cadena2: 1111111111111111111
cadena3: 0123

c2) strncat ( char * destination, const char * source, size_t num );


Cdigo
1. #include <stdio.h>
2. #include <string.h>
3.
4. int main()
5. {
6. char cadena1[]={"aaaa"};
7. char cadena2[100];
8. char cadena3[]={"0123"};
9.
10. printf("Ingrese cadena: ");
11. fgets(cadena2,100,stdin);
12. printf("cadena1: %s\ncadena2: %s\ncadena3: %s\n\n",cadena1,cadena2,cadena3);
13. strncat(cadena1,cadena2,0);//Obviamente esta lnea es un absurdo
14. //Pero queda claro el control que la funcin mantiene
15. printf("cadena1: %s\ncadena2: %s\ncadena3: %s",cadena1,cadena2,cadena3);
16. getchar();
17. return 0;
18. }

Ingrese cadena: 123123


//Antes de strncat
cadena1: aaaa
cadena2: 123123
cadena3: 0123
//Despus de strncat
cadena1: aaaa
cadena2: 123123
cadena3: 0123
Estas ultimas funciones a pesar de poder utilizarse perfectamente como solucin a lo anterior
expuesto, tambin tienen sus contras:
-Tanto strncpy o strncat no proveen un valor de retorno que pueda implicar un error o el xito de la
cadena resultante, si no que devuelven un puntero al buffer destino. Por lo tanto requiere un
esfuerzo extra por parte del programador.
- strncpy no finaliza la cadena resultante con null si el destino es al menos igual en tamao que el
origen, por lo tanto siempre debe finalizarse la cadena con null despus de la llamada a strncpy.
- strncpy tambin tiene un comportamiento que puede afectar el rendimiento del programa en caso que
el buffer destino sea considerablemente ms grande que el buffer origen, ya que en este caso se
realiza el zero-padding, es decir, llena el resto de la cadena con nulls.
Microsoft tiene una lista de funciones baneadas (lo que en Visual Studio se conoce como deprecated)
y obviamente tiene el reemplazo de estas llamndolas funcion_s. Ejemplo: strcpy_s, strcat_s, gets_s
y etc.
Pueden ver las funciones catalogadas como inseguras en este header
(http://download.microsoft.com/download/2/e/b/2ebac853-63b7-49b4-b66f-9fd85f37c0f5/banned.h), y su
reemplazo en esta lista (http://msdn.microsoft.com/en-us/library/bb288454.aspx).
Tambin existen opciones como strlcpy y strlcat, las cuales pueden ver a fondo en este link
(http://en.wikipedia.org/wiki/Strlcpy).

S-ar putea să vă placă și