samedi 13 octobre 2012

Bits, Nibbles et Octets. Tester l'Endianness II

Petit mot sur l'ordre des octets

Le plus grand problème n'est pas l'ordre des bits dans un octet parce que comme on l'as vu dans le poste antérieur les opérateurs de shift binaires sont construits pour l'occulter. Par contre on ne peut pas  ignorer l'ordre dans lequel les octets ("bytes") sont organisés en mémoire ou dans une communication. Ce problème est appelé couramment  "endianness".
En général dans la mémoire d'un ordinateur on va placer un nombre en binaire dans l'ordre inverse des octets. L'ordre des bits comme on l'avait vu déjà  suit généralement l'ordre des octets. Soit le nombre 3 (en base dix) sur quatre octets, pour une structure de mémoire basée sur une unité atomique de 1 octet et d'un incrément d'adresse de 1 octet. 

Le nombre en Big Endian:


byte/octet addr  0    1    2    3  
bit offset  0123456701234567 0123456701234567
binaire00000000000000000000000000000011


Si on le lit en Little Endian directement, la même écriture binaire sera interprétée différemment:

byte/octet addr  3    2    1    0  
bit offset76543210765432107654321076543210
binaire00000000000000000000000000000011


Le nombre lu sera en Big Endian un autre:

byte/octet addr  0    1    2    3  
bit offset  0123456701234567 0123456701234567
binaire1100000000000000000000000000000


Il faut connaître l'ordre pour lire correctement. Il faut renverser l'ordre pour ne pas lire un autre nombre.

Les processeurs Intel (x86 et Pentium) travaillent en mode Little Endian pour les octets, tandis que les processeurs Motorola (la série 680x0)  travaillent en mode Big Endian pour les octets. Le MacOS était Big Endian, et il est devenu Little Endian sur Intel. Si des zones de la mémoire vive contenant des nombres sont copiées telles quelles dans un fichier, donc directement, le fichier ne pourra être lu (sans traitement particulier) que par les ordinateurs ayant une unité centrale travaillant avec le même ordre. Chaque fois qu'un ordinateur accède à fichier audio/vidéo ou un flux multimédia, l'ordinateur a besoin de savoir comment le fichier est construit. Par exemple: si vous écrivez un fichier graphique (comme un fichier .JPEG, qui est Big-Endian format) sur une machine avec un CPU Little-Endian, vous devez d'abord inverser l'ordre des octets de chaque entier vous écrivez ou un autre programme "standard" ne sera pas capable de lire le fichier. Le même problème de l'ordre d'octets se pose dans les transfers par réseau. Les protocoles d'Internet utilisent l'ordre Big Endian pour les octets, appelé donc aussi network byte ordre (ordre des octets du réseau). Il y a des fonctions qui vous aident à changer d'ordre, comme par exemple htonl() et ntohl(), sur 16 et respectivement 32 bits (HostTONework, etc.). 

Bibliographie:

  • Byte and Bit Order Dissection, By Kevin Kaichuan He, Linux Jurnal  
  • Writing endian-independent code in C, by Harsha S. Adiga, IBM developerworks  
  • Endianness White Paper Intel 

/*==============================================================*/
/*==============================================================*/
/*==============================================================*/
/**
 *
 * @Synopsis : Comment a l'air un int en little endian
 **/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

void bits_1byte(unsigned char x)
{
int k;
for(k = 0; k < 8; x <<= 1, k++) 
/* 
de MS bit (most signficant) a LS bit (least significant) pour les humains 
*/
putchar('0' + ((x&128)>>7)); 
}
/* les bits d'un int= 4 bytes*/
void bits_4bytes(unsigned int value)  
{
/*Bytes 0,1,2,3*/
unsigned int B0,B1,B2,B3; 
/*l'octet le plus significatif obtenu avec des shifts*/
B0=(value>>24) & 0xFF
B1=(value>>16) & 0xFF;
B2=(value>>8) & 0xFF;
B3=value&0xFF;
printf("\n");
printf("-------- | -------- | -------- | -------- |\n"); 
bits_1byte(B0);
printf(" | ");
    bits_1byte(B1);
printf(" | ");
bits_1byte(B2);
printf(" | ");
    bits_1byte(B3);
printf(" |\n");
}


unsigned int LittleEndianInt ( ) 
    /* nombre de test: 256+3      */
{
struct { unsigned int
a0:1,a1:1,a2:1,a3:1,a4:1,a5:1,a6:1,a7:1,
b0:1,b1:1,b2:1,b3:1,b4:1,b5:1,b6:1,b7:1,
c0:1,c1:1,c2:1,c3:1,c4:1,c5:1,c6:1,c7:1,
d0:1,d1:1,d2:1,d3:1,d4:1,d5:1,d6:1,d7:1;
}t; 
/* 
&t nous donne l'adresse du premier champ de la struct a0 
et l'adresse la plus basse, donc j'ecris le nombre de test 
256+3 en little endian en partant de &t 
*/
t.a0 = 1;t.a1 = 1;t.a2 = 0;t.a3 = 0;
t.a4 = 0;t.a5 = 0;t.a6 = 0;t.a7 = 0;
t.b0 = 1;t.b1 = 0;t.b2 = 0;t.b3 = 0;
t.b4 = 0;t.b5 = 0;t.b6 = 0;t.b7 = 0;
t.c0 = 0;t.c1 = 0;t.c2 = 0;t.c3 = 0;
t.c4 = 0;t.c5 = 0;t.c6 = 0;t.c7 = 0;
t.d0 = 0;t.d1 = 0;t.d2 = 0;t.d3 = 0;
t.d4 = 0;t.d5 = 0;t.d6 = 0;t.d7 = 0;
return *(unsigned int*)(&t);
/* 
prend l'adresse la plus basse (&t) initialise avec elle 
un pointeur sur un entier (unsigned int*) et recupere 
sa valeur avec l'operateur de déréférencement "*"  
*/
}




unsigned ReverseBitsIntLittleEndian (unsigned int Nb ) 

{
struct { unsigned int
 a0:1,a1:1,a2:1,a3:1,a4:1,a5:1,a6:1,a7:1,
         b0:1,b1:1,b2:1,b3:1,b4:1,b5:1,b6:1,b7:1,
         c0:1,c1:1,c2:1,c3:1,c4:1,c5:1,c6:1,c7:1,
         d0:1,d1:1,d2:1,d3:1,d4:1,d5:1,d6:1,d7:1;
}t; 
/*
en big endian &t nous donne l'adresse du champ de la struct d7 et 
l'adresse la plus haute du nombre  
*/
/* 
les shifts manipulent les puissances de 2  "<<b"=*2^b  ">>b"=1/(2^b)
*/
t.d7 = (Nb & 1);Nb=Nb>>1;t.d6 = (Nb & 1);Nb=Nb>>1;
t.d5 = (Nb & 1);Nb=Nb>>1;t.d4 = (Nb & 1);Nb=Nb>>1;
t.d3 = (Nb & 1);Nb=Nb>>1;t.d2 = (Nb & 1);Nb=Nb>>1;
t.d1 = (Nb & 1);Nb=Nb>>1;t.d0 = (Nb & 1);Nb=Nb>>1;
t.c7 = (Nb&1);  Nb=Nb>>1;t.c6 = (Nb & 1);Nb=Nb>>1;
    t.c5 = (Nb&1);  Nb=Nb>>1;t.c4 = (Nb & 1);Nb=Nb>>1;
t.c3 = (Nb & 1);Nb=Nb>>1;t.c2 = (Nb & 1);Nb=Nb>>1;
t.c1 = (Nb & 1);Nb=Nb>>1;t.c0 = (Nb & 1);Nb=Nb>>1;
t.b7 = (Nb&1);  Nb=Nb>>1;t.b6 = (Nb & 1);Nb=Nb>>1;
t.b5 = (Nb&1);  Nb=Nb>>1;t.b4 = (Nb & 1);Nb=Nb>>1;
t.b3 = (Nb & 1);Nb=Nb>>1;t.b2 = (Nb & 1);Nb=Nb>>1;
t.b1 = (Nb & 1);Nb=Nb>>1;t.b0 = (Nb & 1);Nb=Nb>>1;
t.a7 = (Nb&1);  Nb=Nb>>1;t.a6 = (Nb & 1);Nb=Nb>>1;
t.a5 = (Nb&1);  Nb=Nb>>1;t.a4 = (Nb & 1);Nb=Nb>>1;
t.a3 = (Nb & 1);Nb=Nb>>1;t.a2 = (Nb & 1);Nb=Nb>>1;
t.a1 = (Nb & 1);Nb=Nb>>1;t.a0 = (Nb & 1);Nb=Nb>>1;
return *(unsigned int*)(&t);
}



int  main(int argc, char * argv [])
{
printf("Valeur de test introduite en Little Endian a la main :256+3=259");
unsigned int NbTest = LittleEndianInt();
bits_4bytes(NbTest);
/*
transtypage du int=> char 
je recupere  dans le char l'octet/byte le moins significatif 
 
*/ 
printf("Adresse memoire basse \n");
unsigned char c=0;
c   = *(unsigned char *)(&NbTest);
bits_1byte(c);
printf("\n");
printf("\n");
printf("Le bit de plus faible poids et 1 ou bien 0?\n");
putchar('0' + (c & 1));
printf("\n");
printf("\n");
printf("Votre nombre\n");
unsigned int Nombre;
scanf ("%i",&Nombre);
c=0;
c   = *(unsigned char *)(&Nombre);
printf("\n");
printf("Nombre en binaire \n");
printf("Lowest memory address ");
bits_1byte(c);
printf("\n");
bits_4bytes(Nombre);
printf("\n");
printf("\n");

printf("Bits renverses\n");
unsigned int NbRev=ReverseBitsIntLittleEndian(Nombre);
printf("Adresse memoire basse ");
c=0;
c   = *(unsigned char *)(&NbRev);
printf("\n");
bits_1byte(c);
printf("\n");
bits_4bytes(NbRev);
printf("%u",NbRev);
printf("\n");

  
    return 0;

//  main()
/*==============================================================*/
/*==============================================================*/
/*==============================================================*/

/*

 pcad:dirfile andreea$ ./ex.run 
 Valeur de test introduite en Little Endian a la main :256+3=259
 -------- | -------- | -------- | -------- |
 00000000 | 00000000 | 00000001 | 00000011 |
 Adresse memoire basse 
 00000011

 Le bit de plus faible poids et 1 ou bien 0?
 1

 Votre nombre
 2147483647    

 Nombre en binaire 
 Lowest memory address 11111111

 -------- | -------- | -------- | -------- |
 01111111 | 11111111 | 11111111 | 11111111 |


 Bits renverses
 Adresse memoire basse 
 11111110

 -------- | -------- | -------- | -------- |
 11111111 | 11111111 | 11111111 | 11111110 |
 4294967294

*/

Bits, Nibbles et Octets. Tester l'Endianness I

Petit mot sur l'ordre des  bits


Intel dans son papier appelé Endianness White Paper (pp.12) fait un rappel sur le fait que même les opérations sur les bits sont endian-sensibles. Même un champ de bits défini dans un byte/octet simple est endian-sensible. Le code qui définit des champs de bits, et nibbles (groupe de 4 bits) est sujet à des conflits d'endianness en mettant en conflit la communication sur une plateforme endian opposée.
Malgré cela la <<culture Google>>  française et anglaise nous révèle que un nombre grand de gens disent le contraire. Il y a même  des cours qui le font!!! (sic transit gloria mundi!) Donc un peu de code C peut aider éclairer le sujet.
Lorsqu'on lit un nombre en binaire écrit sur un papier, l'usage fait du chiffre le plus à gauche le plus significatif car associé à la plus grande puissance de 2 dans l'écriture du nombre. La colonne des 8 représente le bit le plus significatif (msb, most significant bit), puisqu'elle contient la plus grande valeur; et la colonne des 1 représente le bit le moins significatif (lsb, least significant bit), puisqu'elle contient la plus petite valeur. L'ordre usuel sur papier est appelé ordre "big endian" (grand boutien).

Donc le nombre 3 (base dix) sur un octet s'écrit en Big Endian est: (msb<-lsb)

byte/octet addr0
bit offset 0123 4567
binaire 0000 0011


L'autre ordre utilisé est l'ordre du petit boutien (en anglais-- little endian bit ordre), le bit le plus à droite étant celui le moins significatif.
Donc le nombre 3 (base dix) s'écrit en Little Endian( lsb->msb)

byte/octet addr
0
bit offset
 7654 3210
binaire
 1100 0000

Le programme ci après se comporte différemment sur une machine little endian et sur une machine big endian, et sait même se "rendre compte" de l'endianness. De plus, il montre comment même l'ordre des bits peut changer d'une telle machine à une autre. En occurrence, sur les machines avec des processeurs Intel, même les bits sont dans l'ordre inverse.
Par exemple, sur un PC AMD Athlon 7750, le programme en question donne ceci:

This is a little endian machine.
---- same ------
01110010
Intended number initially nibble-set, in hexa: 72
---- that is ------
01110010
Number read back from the nibble, in hexa: 4e
---- which is ------
01001110

et on remarque le fait que l'ordre des bits du dernier message est exactement celui inversé par rapport aux autres énumération de la sortie du programme. (111=7 et 10=2).

#include <stdio.h>


void bits(unsigned char a, const char *m) {
    int k;
    printf("---- %s ------\n",m);
    for(k = 0; k < 8; a <<= 1, k++) {
  putchar('0' + ((a&128)>>7)); 
  /* from MSbit to LSbit, for human readability */
    }
    printf("\n");
}

int main(int argc, char **argv) {
    int                 f = -1;
    int                 k = -1;

    const int           n = 0x12345678;
/* get the lowest memory address byte */ 
    const unsigned char b = *(unsigned char *)(&n);
/*   0x72 = 01110010                  */
    const unsigned char p = 0x72;
/*sum2^n*/
    const unsigned char q = 0 * 128 + 1*64 + 1*32
    1*16 + 0*8 + 0*4 + 1*2 + 0*1
    unsigned char       r = 0

    struct {
char a:1,b:1,c:1,d:1,e:1,f:1,g:1,h:1;
    } t;

    if(b == 0x78) {
printf("This is a little endian machine.\n");
    }
    else {
printf("This is a big endian machine.\n");
    }
    
    /* 
   p is 0x72 = 01110010, and '>>' 
   behaves identically regardless of endianness       
*/
    bits(p,"same"); 

    /* 
   however, we can show that the bit order 
   is different on Intel than on e.g. SPARC  
*/

    /* 
    let us put by hand the bits from 
    0x72 = 01110010 into 't', nibble by nibble       
*/
    t.a = 0; t.b = 1; t.c = 1; t.d = 1
t.e = 0; t.f = 0; t.g = 1; t.h = 0

    /* and "read" it back as a char that "it is"               */
    r   = *(unsigned char *)(&t);

    /* and print each of these, as a value and also bit by bit */

    printf("Intended number initially nibble-set, in hexa: %x\n",q); 
/* 0x72 always      */
    bits(q,"that is");

    printf("Number read back from the nibble, in hexa: %x\n",r); 
    bits(r,"which is"); /* not the same as 'q' on little endian*/

    return(0);

}