[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