Archive

Archive for the ‘Hardware’ Category

Little Endian y Big Endian

August 3rd, 2010 No comments

¿De qué va esto del endian? No, no se trata de indios pequeñitos…
El endianness de un procesador indica, básicamente, el orden de almacenamiento de los bytes de las variables de más de un byte en la memoria.
Los dos tipos de endian principales son el Little-Endian y el Big-Endian.
En little-endian los bytes se almacenan en orden inverso al natural, es decir, primero el byte menos significativo (LSB) y de último el más significativo (MSB).
Por ejemplo el número de 32 bits 0×10203040 se almacena así en memoria (cada recuadro representa un byte):

Y el número de 16 bits 0×1020 así:

En big-endian se sigue el orden normal, primero el byte más significativo (MSB) y de último el menos significativo (LSB):

En la plataforma PC (procesadores Intel y AMD) se utiliza little-endian así como en los Z80 y VAX.
Los procesadores Motorola 68000, los PowerPC (de Motorola) y los SPARK (de Sun) son big-endian.
Luego hay procesadores que son configurables y pueden operar en los dos formatos; MIPS, ARM y los SPARC y PowerPC modernos, por ejemplo.

Como regla general si un programa va a ser compilado y ejecutado en diversas plataformas hay que tener en cuenta el endian y programarlo de forma que se detecte el endian del procesador y se actúe en consecuencia en cada caso, de lo contrario te vas a encontrar con una larga serie de preciosos bugs muy difíciles de rastrear y que te pueden dar largas horas de “diversión”.
Hay dos casos principales en donde se debe tener muy en cuenta el endian; el primero es cuando el programa graba/lee archivos y el segundo cuando establece conexiones de red.
El problema en el primer caso es evidente, un ejemplo; con el programa ejecutándose en un PC grabas una serie de estructuras de datos en un archivo, si luego ejecutas el mismo programa en un procesador 68000 y cargas el archivo que previamente habías grabado desde un PC, los datos de las estructuras del archivo se cargarán invertidos ya que el endian de ambas plataformas no es el mismo (se grabaron los datos en little-endian y los lees como big-endian).
Con la programación de red el problema es más simple: todas las transferencias de datos se realizan en big-endian independientemente de la plataforma que las ejecute, éste es el motivo por el cual al formato big-endian también se le conoce como “Network Byte Order”.  Por eso las librerías de sockets incluyen funciones de conversión de endian:

  • htonl(): Host to network byte order (long, 32 bits). Convierte una variable de 32 bits del endian del procesador a formato big-endian.
  • ntohl(): Network to host byte order (long, 32 bits). Convierte una variable de 32 bits de big-endian al endian del procesador.
  • htons()Host to network byte order (short, 16 bits). Convierte una variable de 16 bits del endian del procesador a formato big-endian.
  • ntohs()Network to host byte order (short, 16 bits). Convierte una variable de 16 bits de big-endian al endian del procesador.

Volviendo al problema de la grabación de archivos, tienes dos alternativas para evitar este tipo de problemas:

  • La primera forma es incluir una firma al inicio del archivo que servirá no sólo para identificar el tipo de archivo sino también para saber si fue grabado en una plataforma de distinto endian, y si es así ya sabes que tendrás que leerlo invirtiendo los datos, si por ejemplo usas como firma dos bytes al inicio del archivo como 0xAAFF (un entero de 16 bits) y en otra plataforma al leer el short de la firma se carga como 0xFFAA significa que el programa tendrá que convertir el endian del archivo, si la hubiera leído como 0xAAFF entonces significa que el endian del procesador que grabó el archivo y el endian del procesador que lo ha leído son el mismo y no es necesaria ninguna conversión.
  • La segunda es grabar siempre los archivos en un endian determinado (a ser posible en el endian de la plataforma que se vaya a utilizar con más frecuencia), de esta forma, si por ejemplo has decidido que el archivo sea de formato big-endian lo único que tendrás que hacer es grabarlo y leerlo como big-endian siempre, independientemente de la plataforma. Fácil, no?

¿Y cómo detectas el endian de un procesador? Pues muy fácil, comprobando cómo almacena las variables de más de un byte en memoria:

1
2
3
4
5
6
7
8
enum Endian { LITTLE_ENDIAN, BIG_ENDIAN };

Endian ComprobarEndian()
{
  unsigned i = 1;

  return(((*(char*)&i) == 1) ? LITTLE_ENDIAN : BIG_ENDIAN);
}

Con un puntero char comprobamos el contenido del primer byte en memoria de los cuatro que forman la variable entera i de 32 bits, si el procesador es little-endian el valor 1 estará almacenado en el primer byte de la secuencia en memoria [0x01, 0x00, 0x00, 0x00] y por lo tanto la comparación será verdadera y se devolverá LITTLE_ENDIAN, si el procesador es big-endian el 1 estará en el cuarto byte en la memoria [0x00, 0x00, 0x00, 0x01] y la comparación será falsa devolviendo por lo tanto BIG_ENDIAN.

Para terminar solo queda por ver cómo cambiar el endian de una variable (de little-endian a big-endian y viceversa), esta función cambia variables de 16 bits (short):

1
2
3
4
5
6
7
8
9
short CambiarEndian(short valor)
{
  char *p = (char *)&valor;
  char tmp = p[0];
  p[0] = p[1];
  p[1] = tmp;

  return(valor);
}

Y ésta cambia el endian de variables de 32 bits:

1
2
3
4
5
6
7
8
9
10
11
12
int CambiarEndian(int valor)
{
  char *p = (char *)&valor;
  char tmp = p[0];
  p[0] = p[3];
  p[3] = tmp;
  tmp = p[1];
  p[1] = p[2];
  p[2] = tmp;

  return(valor);
}