PDA

Ver la versión completa : Ayuda leyendo de arrays



Locke
11/10/2004, 21:12
A ver si algun programador caritativo me puede echar una mano, que me estoy volviendo loco con esto.

Explico:

Tengo una funcion que carga desde la smc unas estructuras de datos muy irregulares, la mayoria de las veces acediendo de byte en byte, con lo que se pierde mucho tiempo.

Para evitar esto, mi intencion es cargar el fichero completo en memoria y una vez alli ya hacer la seleccion de datos. Para esto he usado el gpconverter, que convierte cualquier archivo binario en un array de bytes.

Mi funcion original es la siguiente:

unsigned short A;
unsigned int B;

smc_fread (&A, 2, 1, archivo);
smc_fread (&B, 4, 1, archivo);
...

es decir, lee 2 bytes del archivo y los guarda en la variable A, despues lee los 4 siguientes bytes y los guarda en B. Eso funciona bien, el problema viene ahora.

Supongamos que tenemos un array llamado "array" con los datos del anterior archivo. El codigo seria:

unsigned short A;
unsigned int B;
int pos = 0;

const unsigned char array[9730] = {77, 77, 2, 38, 0, 0, .........};

A = ((unsigned short *)array)[pos];
pos += 2;

B= ((unsigned int *)array)[pos];
pos += 4;
...

Ahora el codigo lee bien la primera vez (tiene que leer un 0x4d4d) pero despues no carga correctamente la segunda variable. (lee solo 10 (0x0a) cuando deberia leer 9730 (0x00002602) ).


Un par de cosas: Si le dices que lea un entero de memoria lo lee B.I. o L.I.? Es decir, primero en primer byte, despues el segundo, tercero y por ultimo el cuarto o de manera inversa (4º, 3º, 2º, 1º)?

Por que lee solo 4 bits cuando deberia leer 32, si ademas en el primer caso lo hace bien?


Bueno, muchas gracias, a ver si alguien me echa una mano, que me corre bastante prisa...

theNestruo
11/10/2004, 23:05
El problema puede venir del padding de memoria (alineamiento a byte, word o dword). Prueba utilizando la directiva...


#pragma pack (2)

...para suprimir el padding; o mejor todavía, la pareja...


#pragma pack (push, 2)
/* declaraciones aquí */
#pragma pack (pop)


Respecto a big-endian y little-endian, depende de la arquitectura para la que se compile.

Por otra parte, lo mejor para hacer lo que tú quieres es o utilizar una (o varias) structs para cargar los datos por bloques...


struct aStruct {
unsigned short a;
unsigned int b;
} theStruct;

smc_fread (&theStruct, sizeof (struct aStruct), 1, archivo);

Y por último, también puedes intentarlo con unions...


union anUnion {
struct firstStruct {
unsigned short a;
unsigned int b;
} estructura;
struct secondStruct {
unsigned char byte[sizeof (struct firstStruct)]; /* No recuerdo
si esto se puede hacer; en otro caso
tendrías que poner tú el tamaño a mano */
} array;
} theUnion;

...de modo que lo que cargues en theUnion.array se carga en theUnion.estructura y viceversa.

[edit: ¿Por qué declaras el array de bytes como const?]

theNestruo
11/10/2004, 23:18
Por cierto, (lo acabo de ver)...


unsigned short A;
unsigned int B;
int pos = 0;

const unsigned char array[9730] = {77, 77, 2, 38, 0, 0, .........};

A = ((unsigned short *)array)[pos];
pos += 2;

B= ((unsigned int *)array)[pos];
pos += 4;

...es un código bastante peligroso. De hecho, la posición del array se va a evaluar una vez realizado el casting, por lo que se a la hora de calcular el valor a asignar a B, el puntero va a avanzar 8 bytes (pos * sizeof (unsigned int)=2*4). La forma correcta de hacer lo que tú pretender hacer sería...


A=*( (unsigned short*) (&(array[pos])) );
pos+=sizeof (unsiged short);

Es decir, que a A se le asigne el valor apuntado por un puntero de tipo unsigned short con la dirección de memoria de array[pos] si cambiar de tipo.

Si no te queda más remedio, hazlo así. Pero yo buscaría métodos algo menos propensos a provocar bugs inencontrables (por ejemplo, el de la union o cargando las structs de disco o rellenandolas con un memcpy() a partir de cierta posición del array).

mortimor
11/10/2004, 23:23
Nestruo, en realidad no mapea el fidchero en memoria sino que lo linka al ejecutable. El array const es el fichero binario que esta leyendo, estos ficheros que se almacenan como "recursos" se suelen poner como arrays constantes.

Por otro lado ... no se si el alineamiento en memoria puede ser la causa. Lo que tiene que tener en cuenta es que si hace un cast del puntero al array para tratarlo como un puntero a short el operador indice del array interpreta cada posicion como de 2 bytes y si hace un cast a int interpreta cada posicion como 4. En consecuencia esta accediendo mal al dato, por ejemplo:

//accedemos a la posicion pos*2 bytes
A = ((unsigned short *)array)[pos];
pos += 2;

//accedemos a la posicion pos*4 bytes
B= ((unsigned int *)array)[pos];
pos += 4;


La solucion:

// hacer un cast al asignar, no en el puntero, trataremos las direcciones como celdas de un byte siempre

A = *( (unsigned short *) (array+pos) );



A ver si no me he equivocado. :D

Edit: me equivoque, si, pero ya esta subsanado :D

mortimor
11/10/2004, 23:24
Jur te me has adelantado Nestruo.

theNestruo
11/10/2004, 23:29
Escrito originalmente por mortimor
Nestruo, en realidad no mapea el fidchero en memoria sino que lo linka al ejecutable (...)
Ah, vale. Nunca he utilizado nada parecido y por eso me chocaba. ;)


Escrito originalmente por mortimor
Lo que tiene que tener en cuenta es que si hace un cast del puntero (...). En consecuencia esta accediendo mal al dato, por ejemplo: (...)
Si, eso lo he visto después, releyendo el post (supongo que he escrito mi segundo post mientras tú posteabas el tuyo).


Escrito originalmente por mortimor
A = (unsigned short) array[pos];
A ver si no me he equivocado. :D
Pues ya me haces dudar, pero creo que tu código coge el byte del array ubicado en pos (array[pos]) y lo convierte en un unsigned short... pero siempre devolverá valores entre 0 y 255, porque nada más coge un byte.

[Edit: de nuevo escribía el post mientras tú posteabas el tuyo. ¡Este hilo va a parecer una conversación de sordos! :D ]

mortimor
11/10/2004, 23:37
Pues creo que tienes razon, esta mal :eek:

Si, es mejor por ejemplo:

A = *( (unsigned short *) (&array[pos])); //podria ser esto, aunque no me gusta mucho mantiene lo que habia dicho.

Incluso mejor...

A = *( (unsigned short *) (array+pos));

Locke
11/10/2004, 23:44
Ok, creo que ya he entendido donde esta el error, voy a probar y os cuento que tal. Muchas gracias por haber contestado tan rapido ;)

Locke
12/10/2004, 01:40
Ya va funcionando, ahora uso la ultima linea que me ha dicho mirtimor:

(ahora pongo el array en hexadecimal, asi es mas facil de ver)

const unsigned char array[9730] = {0x4d, 0x4d, 0x02, 0x26, 0x00, 0x00, 0x02, 0x00, .......}

A = *(unsigned short *)(array+pos));
pos += sizeof(unsigned short);

B = *(unsigned int *)(array+pos));
pos += sizeof(unsigned int);

A termina con el valor 0x4d4d (correcto)
B termina con el valor 0x26024d4d (incorrecto, deberia ser 0x00002602)

Es como si 'pos' no hubiera avanzado de posicion, pero si que avanza, lo he comprobado mirando los valores. No me explico por que para coger un int toma dos valores posteriores y dos anteriores a su posicion... :miedo: Sin embargo, si en vez de avanzarlo 2 posiciones lo avanzo 4 (por probar) toma el valor 0x00020000, que si que es correcto...

Voy a mirar un poco mas el codigo, porque seguro que es un error tonto, pero es que me estoy desesperando ya, porque son 4 lineas y mira que se resisten las condenadas... llevo con ellas todo el dia :(

***EDITO***

A ver, me he enterado de lo que pasa. Solo me deja cojer correctamente los datos cuando "pos" es multiplo del tamaño de palabra que quiero coger, es decir, si pos = 0, pos = 4, pos = 8, etc, lo que por otra parte es logico, ya que si le digo que (name + pos) es un puntero a un array de enteros los unicos indices validos seran los que sean multiplos del tamaño del dato.

Alguna solucion monstruos? ;)

**FIN DEL EDIT**

Venga, muchas gracias.

theNestruo
12/10/2004, 02:27
Escrito originalmente por Locke
A ver, me he enterado de lo que pasa. Solo me deja cojer correctamente los datos cuando "pos" es multiplo del tamaño de palabra que quiero coger, es decir, si pos = 0, pos = 4, pos = 8, etc, lo que por otra parte es logico, ya que si le digo que (name + pos) es un puntero a un array de enteros los unicos indices validos seran los que sean multiplos del tamaño del dato.
Eso no me acaba de cuadrar, ya que array es de tipo unsigned char* y pos es de tipo int, por lo que array+pos debería de ser un unsigned char* (el casting se le aplica después, creo) y en principio un puntero puede apuntar a la dirección que quiera. ¿Has probado con la última forma de mi segundo post (la que utiliza la notación array[pos])? ¿Te hace lo mismo?

Cambiando de tercio:
Partiendo de que te deja apuntar a direcciones múltiplo de 4 bytes (pos=0, 4, 8...) y no a otras (prueba a leer un par de char un hacer pos++, a ver), yo sigo pensando que va a ser cosa del padding. Prueba a suprimir el padding con las directivas push y pop pack que te dije antes... en las declaraciones de las variables y/o en la función (para asegurarte de que el problema viene de aquí o no, si quieres, suprime el padding en todo el archivo).

Locke
12/10/2004, 02:32
Yo es que a eso del padding aun no he llegao en C, asi que no me atrevo mucho, no me gusta hacer cosas que no entiendo xDD

Luego pruebo a ver si funciona tu segundo metodo. De momento lo que he hecho ha sido implementarme unas funcioncillas que leen de byte en byte y despues los colocan y los suman, asi se arregla el problema. Si veo que la velocidad de lectura es suficiente entonces no me preocupo mas, pero si sigue siendo lento entonces me tendre que estudiar eso del "padding" :)

Muchas gracias por la ayuda, de verdad, que gente mas maja hay por aqui... :amor:

Locke
12/10/2004, 04:08
Por fin!! Que satisfaccion, las 4:30 y lo consigo despues de todo el dia dandole vueltas...

No me habia fijado en la solucion que me propusiste usando memcpy, la verdad, me habia olvidado completamente de esa funcion y resulta que era la mas apropiada y la mas sencilla...

En fin, muchas gracias, seguro que esta no se me vuelve a olvidar... ;)