Endianness

Ο όρος endianness χρησιμοποιείται στην επιστήμη των υπολογιστών για τον τρόπο αποθήκευσης των σειρών bytes μέσα στην μνήμη του ηλεκτρονικού υπολογιστή. Υπάρχουν δύο τρόποι αναπαράστασης των bytes που αναπαριστάται ένας αριθμός μέσα στην εσωτερική μνήμη του υπολογιστή: η αναπαράσταση big-endian και η αναπαράσταση little-endian. Οι υπολογιστές με επεξεργαστές 80x86 από την Intel όπως και κλώνοι αυτών χρησιμοποιούν αναπαράσταση little-endian ενώ υπολογιστές όπως οι Sun SPARC, Motorola 68K ή η οικογένεια των PowerPC χρησιμοποιούν αναπαράσταση big-endian. Τα πρωτόκολλα δικτύου υπολογιστών κάνουν τους κατάλληλους μετασχηματισμούς δεδομένων ώστε δύο υπολογιστές με διαφορετικό endianness να επικοινωνούν.

Ορολογία 
Η ορολογία Endianness, big-endian ή little-endian προέρχεται από το λογοτεχνικό βιβλίο Τα ταξίδια του Γκιούλιβερ. Μέσα στο βιβλίο περιγράφεται η διαμάχη δύο μυθικών λαών για το πως μπορεί κάποιος να σπάσει ένα βρασμένο αυγό, είτε κτυπώντας το στην μυτερή άκρη είτε στην μέση (δηλαδή μεγάλη/big ή μικρή/little άκρη/endianness του αυγού). Στην διαμάχη αυτή (μέσα στο λογοτεχνικό έργο), όσοι υποστήριζαν την μία ή την άλλη τεχνική χαρακτηρίστηκαν ως "big-endians" ή "little-endians" και από από εκεί η ορολογία υιοθετήθηκε στην πληροφορική.

Περιγραφή 
Οι άνθρωποι αναπαριστούν του ακέραιους αριθμούς στο δεκαδικό σύστημα αρίθμησης με τιμές 0-9 και τοποθετούν το πιο σημαντικό ψηφίo στην αρχή αριστερά (στον αριθμό 2356 το σημαντικότερο ψηφίο είναι το 2 που αναπαριστά τις χιλιάδες, ενώ το λιγότερο σημαντικό ψηφίο είναι το 6). Οι ηλεκτρονικοί υπολογιστές αναπαριστούν τους αριθμούς στο δυαδικό σύστημα αρίθμησης όπου υπάρχου δύο μόνο ψηφία, το 0 και το 1. Η μνήμη του υπολογιστή είναι σχεδιασμένη ώστε να αποθηκεύει ομάδες 8 δυαδικών ψηφίων γνωστά ως bytes (υπάρχουν και υπολογιστές οι οποίοι αποθηκεύουν περισσότερα από 8 δυαδικά ψηφία αλλά είναι σπάνιοι και έτσι μπορεί να γίνει παραδοχή ότι κάθε διεύθυνση μνήμης αποθηκεύει 8 δυαδικά ψηφία, δηλαδή ένα byte ). Ένα byte μπορεί να αποθηκεύσει 0-255 διαφορετικές τιμές (από 0000 0000 έως 1111 1111 τιμές). Ένας ακέραιος αριθμός μεγαλύτερος από 255 θα χρειαστεί περισσότερο από ένα byte για να αποθηκευτεί στην μνήμη του υπολογιστή. Επίσης στους σύγχρονους υπολογιστές με 32bit ή 64bit με σειρά 32 ή 64bit αποθηκεύεται αντίστοιχα σε 4 ή 8 bytes (4*8bits = 32, 8*8bits= 64). Υπάρχουν δύο τρόποι / endianess με τους οποίους μια σειρά bytes μπορεί να αποθηκευτεί στις θέσεις μνήμης (η κάθε θέση αποθηκεύει 1 byte) :

 Big-endian: Το πιο σημαντικό byte αποθηκεύεται στην πιο "μικρη" θέση μνήμης.
 Little-endian : Το λιγότερο σημαντικό byte αποθηκεύεται στην "μικρότερη" θέση μνήμης.

Ο προγραμματιστής κατά τον προγραμματισμό σε μια γλώσσα υψηλού επιπέδου δεν ασχολείται με τον τρόπο που αποθηκεύονται οι τιμές των μεταβλητών (είτε με την μορφή big-endian ή little-endian) μέσα στην μνήμη του υπολογιστή. Το λειτουργικό σύστημα και ο μεταγλωττιστής που θα μετατρέψει τον πηγαίο κώδικα (από τη γλώσσας υψηλού επιπέδου) σε γλώσσα μηχανής (assembly) θα αποφασίσει αν θα χρησιμοποιηθεί big-endian ή little-endian αναπαράσταση.

TCP/IP 
Πρόβλημα με την αναπαράσταση big-endian ή little-endian δημιουργείται όταν ένας υπολογιστής επικοινωνεί με ένα άλλο υπολογιστή μέσω ενός δικτύου και χρησιμοποιεί διαφορετικό endianness. Για να αποφεύγονται τα προβλήματα αυτά στην επικοινωνίες μέσω δικτύου χρησιμοποιούνται τα πρωτόκολλα επικοινωνίας και γίνονται οι απαραίτητες μετατροπές στην κωδικοποίηση ώστε να επιτευχθεί η δικτυακή μεταφορά δεδομένων.

Στο μοντέλο TCP/IP και σε όλα τα επίπεδα επικοινωνίας χρησιμοποιείται η κωδικοποίηση "big-endian". Οποιαδήποτε τιμή 16 ή 32bit μεταφέρεται μεταξύ των επιπέδων του TCP/IP όπως η IP διεύθυνση ή αριθμητικά ελέγχου (checksums) κλπ θα πρέπει να έχουν το σημαντικότερο byte πρώτο (όπως καθορίζεται με την κωδικοποίηση "big-endian"). Η big-endian κωδικοποίηση αυτή ονομάζεται και "network byte order". Υπάρχουν οι συναρτήσεις htonl, htons, ntohl, ntohs στην γλώσσα προγραμματισμού C οι οποίες χρησιμοποιούνται για το μετασχηματισμό μεταξύ των δύο κωδικοποιήσεων endianness για την επίτευξη επικοινωνίας υπολογιστών με διαφορετικό endianness.

Παράδειγμα 
Στο παρακάτω παράδειγμα φαίνονται οι δύο τρόποι αποθήκευσης στην μνήμη. Έστω ότι έχουμε τον δεκαδικό αριθμό 1394523 ο οποίος αναπαριστάται στο δυαδικό σύστημα ως 101010100011101011011 ή 15475B στο δεκαεξαδικό σύστημα αρίθμησης (κάθε ψηφίο στο δεκαεξαδικό αναπαριστά τέσσερα ψηφία της δυαδικής αναπαράστασης και 2 δεκαεξαδικά ψηφία δημιουργούν ένα byte):

Παράδειγμα προβλήματος endianness στην C 
Το παρακάτω κομμάτι κώδικα μεταγλωττίζεται χωρίς λάθη και σε υπολογιστές όπου χρησιμοποιείται little-endian και σε υπολογιστές όπου χρησιμοποιείται big-endian κωδικοποίηση. Το πρόγραμμα αυτό αρχικοποιεί μια δομή με τρεις μεταβλητές (δύο συμβολοσειρές/πίνακες χαρακτήρων και ενός ακεραίου αριθμού) και στην συνέχεια την γράφει στο εξωτερικό αρχείο data.one.
#include <stdio.h>
#include <string.h>

int main (int argc, char* argv[]) {
    FILE* fp;

    // Παράδειγμα μιας δομής δεδομένων 
    struct {
        char one[4];
        int  two;
        char three[4];
    } data;

    // Αρχικοποίηση της δομής data με δεδομένα 
    strcpy (data.one, "foo");
    // Δεκαεξαδικός αριθμός 01234567 
    data.two = 0x01234567;
    strcpy (data.three, "bar");

    // Εγγραφή των δεδομένων σε ένα εξωτερικό αρχείο 
    fp = fopen ("output", "wb");
    if (fp) {
        fwrite (&data, sizeof (data), 1, fp);
        fclose (fp);
    }
}
Οι συμβολοσειρές/αλφαριθμητικά (strings) "foo" και "bar" θα αποθηκευτούν με τον ίδιο τρόπο και σε μηχανήματα όπου χρησιμοποιείται little-endian αλλά και σε υπολογιστές όπου χρησιμοποιείται big-endian. Ο δεκαεξαδικός αριθμός (01234567)16 (δηλαδή ο 0x01234567) θα γραφτεί με διαφορετική κωδικοποίηση σε υπολογιστή little-endian από ότι σε υπολογιστή big-endian. Αυτό αποτελεί πρόβλημα, αν αργότερα το αρχείο αυτό διαβαστεί σε υπολογιστή με διαφορετική κωδικοποίηση endianness.

Έλεγχος endianness στην C 
Ο παρακάτω κώδικας ελέγχει την κωδικοποίηση endianness του υπολογιστή και μας δείχνει πως κωδικοποιείται ο δεκαεξαδικός αριθμός (01234567)16 μέσα στις θέσεις μνήμης του υπολογιστή.
#include <stdio.h>

#define LITTLE_ENDIAN 0
#define BIG_ENDIAN    1

void show_bytes (char *start, int len) {
        int i;

        for (i=0;i<len;i++)
                // %p Εκτυπώνει τον δείκτη \t Tab-κενό %x Τύπωμα τιμών σε δεκαεξαδικό \n νέα γραμμή
                printf("%p\t0x%.2x\n",start+i, *(start+i));
        printf("\n");
}

void show_int (int x) {
        show_bytes( (char *) &x, sizeof(int));
}

int endianness() {
    int i = 1;
    // παίρνει την διεύθυνση του i και την κάνει cast
    // στον δείκτη char *p.
    char *p = (char *)&i;

    if (p[0] == 1)
        return LITTLE_ENDIAN;
    else
        return BIG_ENDIAN;
}

int main (int argc, char* argv[]) {

   int i;

   printf("Ο υπολογιστής χρησιμοποιεί την κωδικοποίηση: ");
   if (endianness()==BIG_ENDIAN)
      printf("big-endian \n");
   else
      printf("little-endian \n");

   i=0x01234567;
   printf("ο αριθμός int i=0x01234567 (δηλαδή ο %i στο δεκαδικό) έχει αποθηκευτεί ως:\n",i);
   show_int(i);
   return 0;
}

Παρακάτω φαίνονται οι θέσεις μνήμης (σε κάθε εκτέλεση του προγράμματος εμφανίζονται διαφορετικές θέσεις) και οι τιμές που αποθηκεύτηκαν. Το λιγότερο σημαντικό ψηφίο του αριθμού (01234567)16 το (67)16 αποθηκεύτηκε στην "μικρότερη" θέση μνήμης, εδώ στην διεύθυνση 0xbfab6ff0 ή (bfab6ff0)16 κάτι το οποίο δείχνει κωδικοποίηση "little-endian".
Ο υπολογιστής χρησιμοποιεί την κωδικοποίηση: little-endian
ο αριθμός int i=0x01234567 (δηλαδή ο 19088743 στο δεκαδικό) έχει αποθηκευτεί ως:
0xbfab6ff0      0x67
0xbfab6ff1      0x45
0xbfab6ff2      0x23
0xbfab6ff3      0x01

Παραπομπές 

Μνήμη υπολογιστή
Μετάδοση δεδομένων