Problema con array de chars en C

VipeR_CS

Edit: Update en #7 xD

Holaquetal,

Estoy un poco bastante frustrado porque llevo no sé ni cuanto dándome cabezazos contra la puta mierda de código este y no termina de funcionar. Lo que necesito hacer es lo siguiente:

Leo un fichero cuyos datos vienen en 2 columnas por linea, tal que así:

CAMPO1 CAMPO2
CAMPO1 CAMPO2
CAMPO1 CAMPO2

Entre cada columna puede haber 1 o varias lineas en blanco o tabuladores. No sé cuantas lineas tendré que leer, pero siempre serán 2 columnas. En la primera lectura lo leo hasta el final y cuento el nº de lineas. Luego multiplico ese número x2 y tengo los elementos para los que debo reservar memoria en el array. Cada linea no tendrá una longitud mayor de 80, por lo que cada elemento no será mayor de 40. Reservo memoria para un array de numero_de_lineas2 filas y cada fila char40 columnas.

Rebobino el "puntero" del fichero al inicio y empiezo la lectura para cortar cada campo y guardarlo en el array. La cosa va de puta madre para el primer CAMPO1, lo que sería el array[0][X]. El tema es que los siguientes campos, pese a estar aumentando correctamente la fila y poniendo la columna a 0, no me los guarda. Suda de todo. Tampoco casca el programa, pero al debuggear se ve que pasa por ahí como guardando cada campo pero realmente en el array no se guardan, sólo tiene 1 fila. La verdad es que no sé qué mierda está pasando, porque para colmo tengo un programa que hice hace unos meses prácticamente igual pero en vez de desde fichero pillaba una frase por teclado y guardaba cada palabra en una fila de un array. Ese funciona y el de ahora no, no fucking sense.

Después de esta chapa, si alguien ha llegado a leer hasta aquí, le pongo los trozos de código que considero relevantes para el asunto, a ver si ve algo raro >.<

FILE* f_entrada;
char **campos;
int fila=0;
int col=0;
char char_actual;

...

while (fgets(linea, 80, f_entrada)) {
	if (strlen(linea) > 1)  
f_num_lineas++; } f_num_lineas *=2; campos = (char**)malloc(f_num_lineas * sizeof *campos); for (int i=0; i<f_num_lineas; i++) campos[i] = (char*)malloc(40*sizeof(char));

...

char_actual=fgetc(f_entrada);  

do {	
	if (isspace(char_actual)) { // Si encuentro un espacio cambio de elemento en el array
		campos[fila][col] = 0;
		fila++;
		col=0;

	do {    // Pasamos de largo todos los espacios o lineas en blanco que pueda haber hasta el próx. char
		char_actual=fgetc(f_entrada);  
	} while (isspace(char_actual));
} else {
	campos[fila][col] = char_actual;  // Guardo el caracter en el array
	col++;
}

char_actual=fgetc(f_entrada);
} while (char_actual != EOF);

El resultado en el array donde deberían estar las palabras justo después del while es el siguiente:

¿Alguna idea? xD Thank you very much.

Khanser

Donde declaras la variable "linea"? Cuando tengo errores raros suelo quitar todo warning y error de mi código no sea que el causante del poltergueist sea eso :p

1 respuesta
VipeR_CS

#2 Se me olvidó poner aquí esa declaración:

	char linea[81];

Tampoco tiene mucho misterio eso, pero la declaro en la misma función dónde cuento el nº de lineas y hago los mallocs (que es distinta a la que guarda las palabras en el array, o debería guardarlas xD). Esos errores de todas formas son muy raros. Si debuggeo paso a paso con el breakpoint en el while del 2º trozo de código que puse en #1, tanto strlen(linea) como linea cogen el valor perfectamente. Sin embargo si pongo el breakpoint más adelante, en la función donde guardo las palabras, me salen esos errores, pero ese es otro tema xD. No tengo ni puta idea del motivo de eso tampoco, pero al ejecutar sin debuggear el programa no peta.

skv

Una pregunta: estás seguro que puede haber cualquier número de lineas en blanco entre los campos? Es que hablas de columnas, pero si pueden estar en diferentes lineas podrías tener lo siguiente:

CAMPO1
CAMPO2
CAMPO1 CAMPO2

y los dos campos del primer registro estarían en la misma columna (por ejemplo). Si no es un requisito, te recomiendo leer linea a linea.

Otro consejo es que es muy ineficiente leer el fichero completo primero solo para contar las lineas. Lo que se suele usar es reservar para una estimación (digamos, los ficheros andan en torno a los 100 registros) y empiezas a leer. Si llegas a los 100 registros leidos, aumentas el tamaño de la memoria reservada (con realloc) a 150 registros (por ejemplo) y continuas leyendo.

Edit: Para que podamos mirar el código en condiciones, deberías hacer lo que se llama un minimal working example. Es decir, más o menos el código que has puesto ahi pero lo pegas en unos ficheros de forma que compile y pones un link aquí. Es decir, de todo tu codigo extraes las partes relevantes para este problema y haces que compilen. Muchísimas veces se encontraras tu mismo los errores al hacerlo.

1 respuesta
VipeR_CS

#4 En principio siempre serían 2 campos por fila. Antes de empezar me plantee hasta 4 posibles soluciones para hacer lo que quería, y al final me decanté por esta pues no sé muy bien porqué xD. Respecto a lo de reservar 100 e ir aumentando pues es una opción sí, pero no sé si sería más fácil o me acabaría por complicar más la vida.

Edit: ok, haré eso que dices de compilar con lo mínimo posible a ver y si sigue igual ya lo pegaré aquí.

1 respuesta
skv

#5 Entonces te recomiendo no leer carácter a carácter y hacerlo línea a línea, de la misma forma que las cuentas. Seria algo así (lo pongo de memoria sin probarlo ni nada).

while (fgets(linea, 80, f_entrada)) 
{
       campo1 = strtok(linea, delimitadores);
       campo2 = strtok(NULL, delimitadores);
}

donde delimitadores es el conjunto de caracteres por los que pueden ir separados los dos campos (probablemente

" \t"

)

En cuanto a lo de reservar primero 100 no es cuestión de facilidad sino de eficiencia, imaginate que te dan un fichero con 10000 lineas y tienes que leerlo una vez solo para ver cuantas hay...

1 respuesta
VipeR_CS

#6 Bueno, eso creo que lo dejaré para la versión 0.2 xD De momento con que funcione me doy con un canto en los dientes, tampoco necesito que sea especialmente eficiente. Haciendo el código mínimo para compilarlo he conseguido que funcione eliminando de la ecuación el conteo de lineas y los mallocs y declarando campos[100][100]. Así se guarda cada palabra perfectamente (bueno y moviendo uno de los getchars del while dentro de un else, que sumaba uno de más en ciertas ocasiones y se comía la primera letra de cada palabra).

El problema por tanto debe estar en los mallocs o en el conteo de lineas. En el conteo de líneas me mosquean los errores esos que salen sabe Dios porqué cuando no pongo el breakpoint antes de contarlas, pero yo creo que está bien. Los mallocs los he hecho de mil fomas diferentes, pero es cierto que en el Watch sólo aparece una línea cuando deberían aparecer 4, que son las líneas que tiene actualmente el fichero de pruebas que estoy usando.

Así que una vez reducido a eso, ¿por qué coño no me reserva la memoria para 4 jodidas filas, si la variable f_num_lineas llega con el valor correcto de 4? >.<

campos = (char**)malloc(f_num_lineas * sizeof *campos);

for (int i=0; i<f_num_lineas; i++)
	campos[i] = (char*)malloc(40*sizeof(char));

Como comparativa, estos son los mallocs que uso en el programa que separaba las palabras de una frase y que funciona perfectamente:

palabras = (char**)malloc(count_palabras * sizeof *palabras);

for (i=0; i<count_palabras; i++)
		palabras[i] = (char*)malloc(21 * sizeof (char));

Esto no tiene sentido ninguno xD

skv

Creo que lo de

sizeof *campos

no es correcto. Prueba con

sizeof(*char)

. Puede que funcione igual como lo tienes, pero en teoría el sizeof solo funciona con tipos de datos. Al ser el tamaño de un puntero, que siempre son 4 bytes (en una arquitectura de 32 bits) igual te lo pilla bien, pero vamos, que lo correcto es lo que puse arriba.

1 respuesta
VipeR_CS

#8 Lo que no se puede es sizeof(*char) (Acabo de probar y no compila). Eso no tiene un tamaño definido, char a secas en todo caso, pero no es lo que necesito. sizeof también devuelve el tamaño de variables además de tipos de dato.

También he probado a poner:

campos = (char**)calloc(f_num_lineas, sizeof *linea);

El resultado es el mismo, una sola fila.

1 respuesta
VipeR_CS

Uhm, esto no me lo esperaba. He debuggeado el otro programa que funcionaba de separar las palabras de la frase, con un watch en el array de palabras, y el resultado es el mismo. Sólo aparece en el watch la primera fila y el resto de palabras se van guardando en el limbo. Sin embargo luego al hacer el printf se imprimen todas correctamente.

Así que he probado lo mismo aquí, he puesto lo siguiente al final del do-while que rellena el array de campos:

int i=0;
while (i<f_num_lineas)
	printf ("%s\n", campos[i++]); 

Y para mi sorpresa imprime las 4 palabras que hay perfectamente. Estoy confuso, pero mientras funcione creo que así se va a quedar de momento xD

skv

#9 Me confundí con la posición del asterisco, es

sizeof(char *)
1 respuesta
VipeR_CS

#11 Así sí, pero igualmente ya funcionaba de la forma que lo tenía. A pesar de no mostrarse bien en el watch, la realidad es que el programa (ya prácticamente acabado) funciona correctamente y se guardan bien las palabras. Misterios de C xD

12 días después
VipeR_CS

Uppeo este por no abrir otro para una pregunta: ¿hay alguna forma de controlar el tamaño de una string que se meta por teclado y que no te pete el programa si excede el tamaño de la variable?

Ejemplo: tengo string[256] donde guardo la entrada con gets. Por mucho que lo meta en un try - catch me peta el programa si meto 300 chars. No se me ocurre una forma de limitar la entrada xD

Usuarios habituales

  • VipeR_CS
  • skv
  • Khanser