[C con Clase] Cadenas estilo C

Salvador Pozo salvador en conclase.net
Jue Ago 8 17:12:57 CEST 2013


El pasado 2013-08-08 11:41:36, Sergio Torró escribió:
 
ST> Muchas gracias Salvador. No sabía que existía un blog de C con Clase! 

Hola:

Por cierto, me estoy dando cuenta de que le pasa lo mismo a prácticamente todo el mundo. :)

ST> ...mi intención con esto era simplemente
ST> comprender el manejo de cadenas estilo C (según he leído hay mucho código
ST> heredado y conviene saber del tema). 

Siempre es bueno comprender el manejo de cadenas estándar C. Como bien dices, hay muchísimo código escrito para ese tipo de cadenas, y muchos programadores que aún las usamos, bien sea por costumbre, por cabezonería o por desconocimiento de otras opciones. 

ST> Por lo que he estado viendo para hacer
ST> esto de una forma ajustada (sin desperdiciar memoria) sería “necesario”
ST> utilizar las funciones de memoria dinámica de C (al fin y al cabo estoy
ST> manejando cadenas estilo C ^^). 

No necesariamente. Los operadores new y delete hacen lo mismo que las funciones C para manejar memoria. Tal vez eches de menos la función realloc, pero es una pérdida menor.

La ventaja de los operadores es que son una abstracción entre el sistema y el programador. Añaden otra capa, ya que es posible sobrecargarlos para implementar otros tipos de control de memoria. Con las funciones C no se puede hacer.

ST> He escrito el siguiente código y lo he
ST> probado y parece que funciona bien en todos los casos (cadenas largas, con
ST> espacios…):

A mi entender hay algunos errores y cosas que se pueden mejorar en tu programa. Comento sobre la marcha.

ST> #include <stdlib.h> //malloc, free, realloc...
ST> static const size_t DEFAULT_CHARS = 2000;

Un tamaño arbitrario, supongo. Resulta difícil pensar que un usuario "normal" introduzca más de 2000 caracteres cuando le pides una cadena, pero puede pasar. Es incluso más probable si el usuario no es "normal", sino un hacker buscando un buffer para desbordar.

Cierto que el método getline impide que se lean más de DEFAULT_CHARS caracteres, de modo que estamos protegidos ante ataques de ese tipo, así que de momento, todo bien. :)

ST> int main(void) {
ST>        const char *concat_str = "este texto se usa para concatenar.";
ST>        char *cad_input = (char*) malloc(sizeof(char) * (DEFAULT_CHARS));
ST>        cout << "Introduce una cadena: ";
ST>        cin.getline(cad_input, DEFAULT_CHARS);
ST>        cad_input = (char*) realloc(cad_input, strlen(cad_input) + 1);
ST>        cad_input[strlen(cad_input)] = '\0';

Es innecesario añadir el terminador nulo, ya que estaba en la cadena original. Más que nada porque si no fuera así, la función strlen no hubiera funcionado. Precisamente, lo que hace esa función, es contar caracteres hasta que encuentra el primer nulo.

ST>        cout << "Cadena introducida: " << cad_input << endl;
ST>        char *final_str = (char*)
ST>              calloc(strlen(cad_input) + strlen(concat_str) + 1, sizeof(char
ST> ));

Me falta un carácter para final_str. Necesitas los suficientes para cad_input, concat_str, para el espacio separador y para el nulo terminador.

ST>        strcat(final_str, cad_input);

Nos pasa lo mismo que en el ejemplo anterior. No puedes asegurar que final_str tenga una cadena vacía nada más crearla. De hecho, lo más frecuente es que no sea así. Hay que inicializar siempre todas las variables antes de usarlas, y esto es más importante cuando trabajamos con punteros y con memoria dinámica.

Al obtener memoria desde el montón (que es desde donde se obtiene la memoria dinámica), no se inicializa el contenido, sólo obtenemos un puntero a la memoria reservada.

Podrías usar la función strcpy en lugar de strcat, o iniciar la cadena, por ejemplo con final_str[0] = 0;

ST>        strcat(final_str, " ");
ST>        strcat(final_str, concat_str);
ST>        //strcat(final_str, "\0");
ST>        cout << "Cadena concatenada: " << final_str << endl;
ST>        free(final_str);
ST>        free(cad_input);
ST>        return 0;
ST> }

ST> La única duda que me surge de esto es si es “bueno” hacer un realloc al
ST> mismo puntero (en este caso cad_input). 

No tiene nada de malo. La pregunta sería si es útil hacerlo. Eso dependerá de cada caso. En este ejemplo, que evidentemente es muy simple, no aporta nada, salvo ahorrar algo de memoria, que de todos modos se libera unos segundos después (o unas milésimas de segundo después). :)

ST> PD: Por si interesa, encontré en la documentación de GNU el código de las
ST> funciones estándar y demás implementaciones. Se pueden ver en el siguiente
ST> enlace: http://gcc.gnu.org/onlinedocs/gcc-4.8.1/libstdc++/api/files.html

Me guardo el enlace, gracias.

Hasta pronto.

-- 
Salvador Pozo (Administrador)
mailto:salvador en conclase.net
Blog con Clase: http://blogconclase.wordpress.com
Con Clase: http://conclase.net


Más información sobre la lista de distribución Cconclase