[C con Clase] Cadenas estilo C

Salvador Pozo salvador en conclase.net
Mar Ago 6 20:47:48 CEST 2013


El pasado 2013-08-06 16:48:20, Sergio Torró escribió:
 
ST> Muy buenas a todos. Me gustaría comentar una duda (seguramente muy tonta)
ST> sobre el manejo de cadenas estilo C. Tengo el siguiente código que funciona
ST> perfectamente:
ST> #include <iostream>
ST> using std::cout;
ST> using std::endl;
ST> #include <cstring>
ST> using std::strcat;
ST> int main(void) {
ST>        char cadena1[] = "Hola";
ST>        char cadena2[] = "Perico";
ST>        strcat(cadena1, " ");
ST>        strcat(cadena1, cadena2);
ST>        cout << cadena1 << endl;
ST>        return 0;
ST> }
ST> Echándole un ojo a la declaración de strcat veo que sus parámetros son
ST> punteros (char*, const char*) y se me ocurrió probar a declarar mis dos
ST> cadenas de caracteres como tales: char *cadena1 = "Hola"; Si hago ese
ST> cambio, no funciona.
ST> Creo entender la diferencia entre un array de chars y un puntero: si no me
ST> equivoco el array reserva espacio para cada char que contiene la cadena. Si
ST> lo declaro como un puntero, se reserva espacio para almacenar simplemente
ST> un puntero que puede apuntar a cualquier cadena de chars. Mi duda es: si
ST> esa función está esperando un puntero a una cadena, ¿no debería funcionar
ST> si le paso eso mismo? ¿No se supone que el nombre del array (en este caso
ST> cadena1) es un puntero al primer elemento de la cadena? 

A ver si puedo explicar con claridad lo que está pasando en este ejemplo.

Cuando declaras las cadenas como arrays, el compilador, como bien dices, reserva espacio suficiente para contener la cadena declarada.

El problema no es tanto cuanta memoria reserva el compilador como dónde la reserva.

Cuando se trata de un array, la memoria para las cadenas se obtiene de la pila, y los literales de inicialización, en este caso "Hola" y "Perico" se copian desde su origen inicial a las direcciones asignadas a los arrays.

En este caso, el programa funciona porque se dan algunas circunstancias "afortunadas".

Una de ellas es que las cadenas son cortas, pero al copiar una cadena a continuación de la otra, estamos corrompiendo la memoria que no pertenece al array, y por lo tanto, corrompemos la pila. Como son cadenas cortas, tenemos al suerte de no corromper dados sensibles, pero si fueran cadenas más largas esto no sería así.

La otra es el orden en que hemos declarado las variables.

Prueba esto:
----8<------
#include <iostream>
using std::cout;
using std::endl;

#include <cstring>
using std::strcat;

int main(void) {
       char cadena1[] = "Hola";
       char cadena2[] = "Perico";
       cadena2[6] = ' ';
       cout << cadena2 << endl;
       return 0;
}
----8<------

La salida, en este caso, es "Perico Hola". Esto es porque al almacenarse en la pila, las variables se crean de arriba a abajo, es decir, cada variable local tiene direcciones de memoria menores que las anteriores. El resultado es que primero se almacena en la pila "Hola", y a continuación, en posiciones correlativas, pero menores, "Perico", con el carácter nulo terminador.

En este programa hemos sustituido el terminador de "Perico" por un espacio, y por eso se concatenan (aparentemente) las dos cadenas.

Si declaras las variables en otro orden, probablemente destruyas una al intentar concatenarlas.

Cuando sustituyes las cadenas por punteros, los punteros se siguen almacenando en la pila, pero en lugar de copiar las cadenas al inicial el programa, sólo copia las direcciones. Las cadenas se almacenan junto con el programa ejecutable.

La mayor diferencia en este caso es que el sistema operativo protege las direcciones de memoria donde se almacena el programa para que no puedan ser modificadas, o al menos para detectar cuando han sido modificadas. Esto es lo que provoca la excepción "segmentation fault".

Una pista en este caso son los mensajes de aviso del compilador:

\main.cpp|9|warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]|

Ya te está avisando que estás intentando convertir una cadena constante en un puntero a char, y que eso está desaconsejado.

Constante significa que no puede modificarse su valor, y eso es precisamente lo que intentas hacer con strcat.

En cualquiera de los dos casos estamos diseñando mal el programa, ya que intentamos copiar cadenas usando objetos (punteros o arrays), que no tienen la capacidad suficiente para contenerlas.

ST> He pensado que
ST> tiene que ver con la implementación de la función strcat la cual me
ST> gustaría ver, ¿hay alguna forma donde pueda ver la implementación de dichas
ST> funciones? Vengo de Java y estoy muy acostumbrado en Eclipse a hacer un
ST> “Control + clic” en el elemento que quiero ver su implementación ^^
ST> Imagino que para el compilador de GNU no será un problema ver la
ST> implementación (¿simplemente descargar las fuentes verdad?). ¿Es posible
ST> hacerlo también en Visual Studio? Uso Code::Blocks y Visual Studio a partes
ST> iguales.


No he encontrado los fuentes para strcat, tampoco he buscado mucho, la verdad. :) Sobre todo porque la implementación de esa función no tiene importancia en este caso.

Una posible implementación podría ser:

----8<------
char *strcat(char *dst, const char *src)
{
    char *ret = dst;

    for (; *dst; ++dst);
    while ((*dst++ = *src++) != '\0');
    return ret;
}
----8<------

Sin embargo, estas funciones ni siquiera tienen por qué estar escritas en C, ya que generalmente se trata de librerías de uso frecuente, suelen estar muy optimizadas, y frecuentemente se escriben en ensamblador.

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