[C con Clase] Array de cadenas de caracteres y p aso de paráme tros en C.

srd4121 en njit.edu srd4121 en njit.edu
Mie Dic 10 22:25:45 CET 2008


Hola gmh,

Mensaje citado por: gmh2000 <helder1986 en gmail.com>:

> Gracias a todos. Me he comido bien el coco pero parece que cada vez la
> solución va a peor. 
> 
> Da un warning y desde luego no funciona:
> file_names->string_pointers = &d-> d_name;
> 

Esto es porque un array no es una variable com las demás. Es decir, un array 
no existe en memoria; es más bien una constante: la dirección de memoria del 
primer elemento. Por ejemplo,

char szNombre[] = "Juan";
char *ptr = szNombre;

En la memoria, sólo tenemos esos caracteres. Te pongo lo que se llama un 
vector descriptor para que lo entiendas:

Dirección
de Memoria   Tipo   Nombre   Valor
---------------------------------------
0x33DDAA00   char   ----     'J'     // szNombre = szNombre[0]
0x33DDAA01   char   ----     'u'
0x33DDAA02   char   ----     'a'
0x33DDAA03   char   ----     'n'
0x33DDAA04   char   ----     '\0'
...
0x99CCEE00   char * ptr      0x33DDAA00
...

Como puedes ver, 'szNombre' no aparece por ninguna parte, pero sí aparecen los 
caracteres de la cadena al igual que el puntero, 'ptr', porque éstas sí son 
variables que ocupan espacio en memoria.

> No da errores pero la salida son cientos de caracteres sin sentido:
> file_names->string_pointers = (char (*)[11]) d-> d_name;
> 

Aquí, intentaría alcanzar los caracteres que están en una dirección de memoria 
la cual fue interpretada por los mismos caracteres de la primera cadena. En el 
ejemplo que puse, intentaría recoger el valor de los 4 primeros 
caracteres: "Juan". El valor, en una máquina de Intel, vendría a ser 
0x6E61754A. Por lo tanto, estarías intentando mostrar la cadena a partir de 
esta dirección de memoria. Si aparece algo, has tenido suerte de que se te 
permitiera acceder a esta zona de la memoria sin que el sistema operativo se 
quejara.

> Sin errores de compilación pero el programa casca y se cierra solo:
> strncpy(file_names->string_pointers[file_names->number_of_strings++],
> d-> d_name, 11 );
> 

Aquí tienes el mismo problema anterior, pero que ahora has intentado escribir 
en esa zona de memoria que no era tuya. El S.O. obviamente te ha denegado tal 
operación.

> Todas estas soluciones son SIN usar malloc(). Lo he intentado con esta
> función pero me da el error de siempre sobre los tipos en las
> asignaciones (en el código  he puesto mi intento más lógico).
> 
> Por otra parte no entiendo para que hay que reservar memoria con
> malloc() y/o cuando y porqué hay que liberarla con free().
> 

Necesitamos reservar memoria cuando necesitamos guardar información y/o 
queremos guardar información en otra zona de memoria más amplia que la que 
contiene nuestro programa.

Cuando declaramos variables en nuestro programa, el compilador se encarga de 
asegurar que se gestionen correctamente. De hecho, la memoria que ocupan estas 
variables forma parte de la memoria que contiene tu programa. Por ejemplo,

int main()
{
  char szPalindromo[] = "Dábale arroz a la zorra, el abad";

  return 0;
}

Este programa ocupa 33 bytes y pico debido a la cadena de 
caracteres, 'szNombre'. En este otro ejemplo, el programa ocupa menos:

int main()
{
  char *ptr = (char *) malloc( 33 );

  free( ptr );
  return 0;
}

Aquí el programa ocupa 4 bytes y pico porque un puntero suele ocupar 32 bits. 
Sin embargo, nuestro programa pide 33 bytes de memoria al S.O.. Normalmente, 
esta memoria reservada dinámicamente suele existir en otra zona fuera de 
cualquier programa y gestionada por el S.O..

Como hemos adjudicado memoria explícitamente, es nuestra responsabilidad 
gestionar su desadjudicación (o liberación) invocando 'free()'. De lo 
contrario, ahí se quedará la memoria reservada, porque no pertenece a nuestro 
programa. Hoy en día los SS.OO. son un poco más sofisticados y saben qué 
programas han hecho peticiones y posiblemente liberen la memoria en el peor 
caso de que el programa termine sin liberar la memoria previamente adjudicada.


En el caso de usar punteros y memoria dinámica, solemos hacer esto cuando no 
sabemos de antemano la cantidad de memoria que necesitamos. En tu caso, 
quieres modificar la cantidad de memoria dinámicamente (en tiempo de 
ejecución), porque no sabes de antemano cuántas cadenas van a existir. Esto 
significa que deberás pedir memoria en un principio y luego volver a pedir más 
por cada cadena que quieras agregar a tu lista de cadenas. Aquí, el compilador 
no te puede resolver la vida, porque necesita conocer todos los datos antes de 
poder compilar. Esto implica que te toca a ti gestionar toda esta parte de 
pedir y liberar memoria.

> Recuerdo que la versión en la que asignaba memoria de la forma "facil"
> con un array medio-funcionaba. Me explico: cuando pedía que me mostrara
> las cadenas algunas veces mostraba lo deseado, y otras veces algunas
> cadenas correctamente y otras en chino. Haber si esto ayuda algo para
> que me ayudeis. ¿Esto puede ser debido a que la cadenas las copiaba por
> referencia y no por valor?
> 
> Otra pregunta: ¿al hacer la copia por referencia el recolector de basura
> no elimina el resto de la estructura donde está contenido d->d_name?
> 

No existe recolector automático de basura en C ni en C++. Mencionaste que usas 
Pidgin, por lo que no te puedo decir si éste gestiona tu programa de una 
manera diferente, agregando su propio recolector automático de basura. En 
general, esto no existirá en un programa de C/C++.

> Haber si me podeis echar el vigésimo-noveno cable. Quizás sea mejor
> partir de la solución que medio funcionaba, pero supongo que sería muy
> bueno quedarme con todos estos conceptos claros para un futuro, en
> cualquier caso...
> 

Veamos. Lo que quieres es crear una lista de cadenas, pero no sabes cuántas 
cadenas hay, aunque sí tienes una idea de la longitud de cada cadena. En 
primer lugar, sugiero usar punteros dobles en lugar de punteros a arrays. Esto 
sería,

struct strings_list
{
  char **pLista;
  unsigned int number_of_strings;
};

Ahora tendrás que ir creando cada "entrada" en tu lista además de crear la 
memoria para cada cadena de caracteres. Por ejemplo,

// Memoria para la lista: 1 cadena
file_names.pLista = (char **) malloc( sizeof(char *) );
file_names.pLista[0] = (char *) malloc( 11 );  // Memoria para 11 caracteres

// Memoria para la lista: 2 cadenas
file_names.pLista = (char **) malloc( 2*sizeof(char *) );
file_names.pLista[0] = (char *) malloc( 11 );  // Memoria para 11 caracteres
file_names.pLista[1] = (char *) malloc( 11 );  // Memoria para 11 caracteres

// Memoria para la lista: 3 cadenas
file_names.pLista = (char **) malloc( 3*sizeof(char *) );
file_names.pLista[0] = (char *) malloc( 11 );  // Memoria para 11 caracteres
file_names.pLista[1] = (char *) malloc( 11 );  // Memoria para 11 caracteres
file_names.pLista[2] = (char *) malloc( 33 );  // Memoria para 33 caracteres

// Memoria para la lista: 4 cadenas
file_names.pLista = (char **) malloc( 4*sizeof(char *) );
file_names.pLista[0] = (char *) malloc( 11 );  // Memoria para 11 caracteres
file_names.pLista[1] = (char *) malloc( 11 );  // Memoria para 11 caracteres
file_names.pLista[2] = (char *) malloc( 33 );  // Memoria para 33 caracteres
file_names.pLista[3] = (char *) malloc( 5 );   // Memoria para 5 caracteres

El problema es que tienes que ir haciendo esto en cada iteración. La solución 
es usar 'realloc()'. Esta función te readjudica una nueva cantidad de memoria. 
Si la nueva cantidad es mayor a la original, entonces la información original 
permanece "en su sitio". Esto ayuda mucho a este tipo de problemas de ir 
agrandando una lista de elementos. Por ejemplo,

// Agregamos la cadena, 'szNombre', a la lista, 'file_names.pLista'
if( agrandar() )
{
  file_names.pLista = (char **) realloc( file_names.pLista,
                               ++file_names.number_of_strings*sizeof(char *) );
  file_names.pLista[file_names.number_of_strings-1] =
                      (char *) malloc( strlen(szNombre)+1 );
  strncpy( file_names.pLista[file_names.number_of_strings-1], szNombre,
           strlen(szNombre) );
}

Aconsejo meter todo esto en otra función para que no te hagas más líos de los 
que se puedan provocar a estas alturas.

> Otra duda conceptual es que cuando pongo a las cadenas una longitud de
> 11, como se ve en el código, es porque la inmensa mayoría tienen 10
> caracteres. Reservo 11 porque creo recordar que el último caracter es el
> /0. ¿Esto lo hago bien? ¿Puede ser el causante de los problemas esto?
> 

Esto es correcto.


Espero que todo esto te aclare las dudas y el asunto.

Steven





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