Buscar elemento en array dinámico en C

VipeR_CS

Bueno, empiezo explicando el problema.

Supongamos que tengo un fichero de entrada con texto. De ahí necesito guardar todas las palabras distintas en un array, sin que se repitan. Creo que está bastante claro pero pongamos un ejemplo práctico.

Si la primera línea fuera: "Hola que tal adiós hola pepe tal", en el array debería guardar, por este orden: Hola, que, tal, adiós, pepe.

El problema en mi código es que pese a que la búsqueda y tal la hace correctamente, acaba petando por un sitio o por otro porque debo estar realocando mal el tamaño, me imagino. Ayer me petaba al guardar un elemento, hice unos cambios y me petaba al acceder a un elemento, y ahora mismo estoy en el punto en que me peta al hacer los free().

Así que no me enrrollo más y os dejo el código relevante, a ver si veis algo mal o tenéis una manera mejor de hacerlo (la verdad es que yo me complico de forma exagerada para cualquier chorrada).

// Declarados globalmente
int num_lineas = 0;
char **constantes;
char **valores;


constantes[0] = (char*)malloc(40*sizeof(char));
valores[0] = (char*)malloc(80*sizeof(char));

// En una función que se llama desde la que contiene los malloc de arriba:

if (num_lineas == 0) 
        num_lineas++;

escribir_cte = false;         

for (int j=0; j<num_lineas; j++) {	
	if (strcmp(valores[j], result) == 0)   
		j = num_lineas;	
	else {								  
		if (j == num_lineas-1) {           
			constantes = (char**)realloc(constantes, num_lineas * sizeof *constantes);  
			valores = (char**)realloc(valores, num_lineas * sizeof *valores);

			constantes[num_lineas] = (char*)malloc( 40*sizeof(char));		
			valores[num_lineas] = (char*)malloc(80*sizeof(char));

			valores[j] = result;  
			escribir_cte = true;       

			num_lineas++;
			j = num_lineas;
		}
	}
}

// De nuevo en la función de los primero malloc

for (int i=0; i<num_lineas; i++) {
	free(constantes[i]);
	free(valores[i]);
}
		
free(constantes);
free(valores);

No sé si me habré dejado algo por copiar. En realidad como veis son dos arrays pero bueno, es irrelevante, el procedimiento es prácticamente el mismo para ambos. A ver qué se os ocurre. Gracias.

VipeR_CS

Debido al tremendo éxito de #1, voy a simplicarlo. He estado cambiando varias cosas con los consejos de un amigo, y tengo localizado un problema: los realloc no me aumentan el tamaño del array. Este es el código implicado, bastante menos que antes.

constantes = (char**)realloc(constantes, num_lineas+1 * sizeof(int));
valores = (char**)realloc(valores, num_lineas+1 * sizeof(int));
printf("%d", sizeof(constantes));
printf("%d", sizeof(valores));
constantes[num_lineas] = (char*)malloc(40*sizeof(char));
valores[num_lineas] = (char*)malloc(80*sizeof(char));

Cuando num_lineas vale 1, los printf muestran "4", el tamaño del int. Cuando num_lineas vale 2, me siguen mostrando 4. Como resultado, cuando num_lineas vale 2 y hace el realloc, pierdo el valor de lo que había en constantes[1] y valores[1].

Si en el realloc pongo num_lineas+2, entonces pierdo el valor de constantes[2] y valores[2] al hacer el realloc con num_lineas=3, y así sucesivamente.

La verdad es que soy incapaz de ver el problema y será una chorrada. A ver si esto lo ve alguien >.<

#3 Well fuck. Pues seguiré buscando entonces, pero en algo influye el realloc porque cuanto más aumento el num_lineas+1 más me tarda en machacar los datos. No parece que esté reservando bien.

1 respuesta
elkaoD

#2 sizeof(constantes) = sizeof(char **)

El sizeof en array sólo funciona si el tamaño es fijo en tiempo de compilación.

Ese no es el problema.

1 respuesta
LOc0

Sólo un par de apuntes. Así a primeras, me parece que se te ha olvidado al principio del todo reservar memoria para los punteros dobles.

Lo digo porque:

int num_lineas = 0;
char **constantes;
char **valores;


constantes[0] = (char*)malloc(40*sizeof(char));
valores[0] = (char*)malloc(80*sizeof(char));

Ahí declaras los punteros dobles que apuntan a no se sabe dónde y justo después haces una asignación usando un puntero que no sabes donde apunta (lo raro es que no te pete ahí). Tienes que reservar primero memoria para los punteros dobles.

char loquesea = (char)malloc(num_punteros, sizeof(char*));

loquesea es un array de punteros char* (o un puntero a un array de punteros, lo que te guste más)

Luego ya podrás ir reservando memoria para cada cadena

loquesea[r]=(char*)malloc(tam_cadena, sizeof(char));

el realloc imagino que lo quieres hacer para meter más cadenas, así que debería ser:

loquesea=(char**)realloc(loquesea, (num_punteros +1)sizeof(char));

Me tengo que pirar.

¡Suerte!

PD: los frees de en sentido inverso. Un buclecillo para las cadenas y luego un free del puntero doble.

1 1 respuesta
cabron

Yo es que la verdad no tengo claro como lo intentas hacer, creo que problema está en la forma que lo has planteado, más que los detalles de si el realloc está bien o mal. Te pongo un ejemplo de una forma que en mi opinión es mucho más evidente, incluso aunque haya algo mal, es mucho más fácil averiguar donde y por qué. No pretendo resolverte lo que estés haciendo, pero me imagino que si te digo que la estructura no es buena y no te pongo un ejemplo de algo mejor no te vale de mucho.

http://pastebin.com/prDfgdrC

2 respuestas
VipeR_CS

#4 el malloc de los punteros dobles lo tengo, se me ha debido pasar ponerlo en el código entre tanto copy&paste.

Luego lo del realloc, la diferencia que veo es que pones sizeof(char*) en lugar de sizeof(int). Según me comentó mi amigo, es equivalente porque el puntero a char no es más que una dirección de memoria y por tanto es un entero al fin y al cabo. Gracias igualmente por las correcciones :)

#5 Ahora mismo me pongo a echarle un ojo a ese código y a ver qué se puede hacer. La verdad es que en mi cabeza parecía fácil el método que había pensado, pero luego en la práctica me he acabado liando yo sólo y no hago más que poner parches sobre parches. Me vendrá bien darle un nuevo enfoque, gracias.

VipeR_CS

#5 Doblepost para que te llegue el aviso. He probado tu código adaptado a mis necesidades y parece funcionar correctamente. Lo que ocurre es que nunca entra por la parte de la primera palabra if(*listaPalabras == NULL). Aún así la primera vez hace el realloc y funciona correctamente de todas formas. El problema es que me siguen petando los free y quizá tenga algo que ver. Que yo sepa los free están en el orden correcto:

for (int i=0; i<num_constantes; i++) 
	free(constantes[i]);

for (int i=0; i<num_valores; i++)
	free(valores[i]);
		
free(constantes);
free(valores);

De todas formas es un problema "menor" puesto que tras los free terminaría el programa, así que se va a liberar igualmente. Ya es sólo por saber porqué mierda me fallan :\

Edit: no he dicho nada con lo de la primera vez. Era yo que me había dejado los malloc de los punteros dobles más arriba. Igualmente sigue petando en cuanto hace el primer free.

1 respuesta
LOc0

Luego lo del realloc, la diferencia que veo es que pones sizeof(char*) en lugar de sizeof(int). Según me comentó mi amigo, es equivalente porque el puntero a char no es más que una dirección de memoria y por tanto es un entero al fin y al cabo. Gracias igualmente por las correcciones

Mmmmm, no siempre :( En x32 sí se cumple que el tamaño de un puntero es el mismo que el de un entero, pero en x64, el tamaño de un int (a secas, no un int long) puede ser 4 y el del puntero 8.

Un free sólo puede petar al intentar liberar algo más de una vez o en una zona protegida, así que a revisar despacito. (Una buena costumbre es poner a NULL un puntero justo después de pasarlo por free).

Salu2 ;)

cabron

#7

Lo primero y más importante, sobre el código que te he dado, generalmente pienso que es mala idea resolver una duda dando directamente la solución completa, porque no se aprende nada, es mejor explicar cual es el problema, y si acaso poner un pequeño ejemplo demostrando la solución, pero como te he comentado, creo que tus problemas venían por como lo estabas haciendo, que era complicado de seguir los pasos que se hacían y buscar el error, y por eso para demostrarte otra solución mejor que te convenza te he puesto una funcionado, pero lo importante es que entiendas por qué es mejor.

Cuando tengas una función, es importante que los pasos que hagas los puedas explicar con palabras, y que al hacerlo, quede algo que esté totalmente claro y que tenga sentido. Piensa en el ejemplo que te he puesto, se podría explicar así:

  1. Separar una palabra usando los espacios en blanco como delimitador
    2.1 Si no se ha añadido ninguna palabra, reservar memoria y añadirla
    2.2 Si ya se han añadido palabras, comprobar si esta ya se ha añadido
    2.2.1 Si la palabra no se ha añadido, reservar memoria para la nueva palabra mantenido lo anteriormente guardado, y añadir la palabra
  2. Volver a 1 para continuar con la siguiente palabra hasta que no haya más palabras

Es tan sencillo y tan evidente, que si en lugar de un programa fuesen una instrucciones para una persona, lo podría hacer igual sin problemas. No hace falta que los escribas ni que se los cuentes a alguien, con que mentalmente lo veas es suficiente. Las complicaciones vienen cuando intentas hacer algo sin saber muy bien como lo estás intentando hacer, por prueba y error, o cuando tenías una idea clara, y al intentar implementarla descubres que hay detalles que no has tenido en cuenta. En este último caso, empiezas a parchear, y una solución que parecía clara acaba siendo un "hago esto, pero solo si se cumple tal condición, y además esta otra, y entonces le sumo un 3, no sé por que hace falta pero si no lo hago no funciona". En ese caso lo mejor es dejar la solución inicial y buscar una nueva. Ten en cuenta que al procesador se la pela, simplemente ejecuta instrucciones, le da igual que tengan sentido o no, pero a una persona no, si no haces algo que tú mismo no puedes entender y seguir paso a paso como funciona, no vas a poder encontrar y arreglar los errores que tenga.

Y lo segundo, sobre los free, te fallan por que hay un error en el código (no está hecho a posta, no me he dado cuenta por no ponerlos), pero es relativamente "fácil" de arreglar. Lo de fácil lo pongo entre comillas por que es un cambio insignificante, hay que cambiar 2 líneas (y se podría reducir a una refactorizando un poco), pero también te digo que es algo un poco rebuscado, lo típico que le das vueltas y vueltas y parece que está todo bien hasta que ves un pequeño detalle y te cagas en spm de que no funcione por esa gilipollez. Intenta buscarlo a ver si lo ves.

1 respuesta
10 días después
VipeR_CS

#9 Más de una semana después, sigo sin ver el puto error xD Deduzco que tiene que ser cosa de algún malloc o realloc, pero yo los veo bien T_T. Se lo he comentado a un amigo y tampoco ve nada. Lo he dejado pasar porque al fin y al cabo no eran necesarios los free al terminar el programa, pero ahora tras añadir algunas cosas me está fallando el programa al sacar la build release y la debug va bien, así que me gustaría descartar que el problema fuera este. ¿Dónde cojones está el fallo? xD

1 respuesta
cabron

#10

El error que había en el ejemplo que te puse está en las dos líneas donde se hace esto:

(char*)malloc(sizeof(char) * strlen(palabra));

strlen() devuelve la longitud de la cadena sin contar el carácter nulo, por lo que se está reservando un byte menos para cada palabra. Lo correcto es:

(char*)malloc(sizeof(char) * (strlen(palabra)+1));

Por que peta en el free es difícil de decir, ya que depende de como el compilador se encargue de llevar la cuenta de lo que se ha reservado con malloc(). Si te fijas a free() no se le pasa el número de bytes que hay que liberar, solo se le pasa el puntero que te devolvió malloc(), es algo que se gestiona de forma interna.

En cualquier caso, cuando haces el strcopy(), estás copiando la palabra a una zona de memoria que tiene un byte de menos, con lo cual el carácter nulo se copia en una zona que puede tener cualquier cosa, y eso parece que acaba rayando al free() a la hora de liberar ese bloque de memoria.

En teoría esto debería hacer que el programa no funcionase, pero al ser algo tan pequeño que usa muy poca memoria, lo más probable es que se esté escribiendo en alguna zona del programa que esté sin usar por nada, y por eso la cadena acaba copiada y se imprime correctamente sin problemas.

1 respuesta
VipeR_CS

#11 Joder como para ver eso, en fin, bastante estaba pensando yo en el carácter nulo. Efectivamente ya no peta en los free, y como me suponía no tenía nada que ver con el problema que tengo ahora. Al menos ya he podido descartarlo. Gracias.

Usuarios habituales

  • VipeR_CS
  • cabron
  • LOc0
  • elkaoD