Endianita

Z Wikipedie, otevřené encyklopedie
Skočit na: Navigace, Hledání

Endianita (pořadí bajtů, anglicky byte order) je v informatice způsob uložení čísel v operační paměti počítače, který definuje, v jakém pořadí se uloží jednotlivé bajty číselného datového typu. Jde tedy o to, v jakém pořadí jsou v operační paměti uloženy jednotlivé řády čísel, které zabírají více než jeden bajt.

Endianita a kompatibilita[editovat | editovat zdroj]

Endianita je jedním ze základních zdrojů nekompatibility při ukládání a výměně dat v digitální podobě. Je nutné brát ji v úvahu při přenášení binárních souborů nebo při síťové komunikaci mezi různými platformami. Tento problém pramení z toho, že stejný zdrojový kód zkompilovaný pro počítače s různými procesory může kvůli jejich různé endianitě produkovat při ukládání nebo přenosu různá binární data. Nejrozšířenějším kódováním vícebajtových dat je v současnosti little endian, což je dané masovým rozšířením architektury Intel x86.

Zdrojem zmatků může být rovněž specifikace IEEE 754, která nedefinuje, v jakém pořadí bajtů se mají ukládat čísla v plovoucí řádové čárce. Endianita může způsobovat problémy i při práci s texty v kódování unicode, proto je rozumné tyto texty ukládat v kódování UTF-8, které je nezávislé na architektuře počítače.

Některé multiplatformní programy (např. OpenDocument nebo konkurenční Office Open XML) řeší problémy s hardwarovou endianitou tím, že ukládají data ve formě textů. I když problémy s kódováním se mohou vyskytnout i u textů, jsou mnohem snadněji řešitelné, protože nejsou dány hardwarem, ale pouze konvencí. Ve zmiňovaném případě OpenDocument a OOXML je tato konvence určena ISO normou.

Little-endian[editovat | editovat zdroj]

V tomto případě se na paměťové místo s nejnižší adresou uloží nejméně významný bajt (LSB) a za něj se ukládají ostatní bajty až po nejvíce významný bajt (MSB). Architektury uplatňující tento princip se nazývají little-endian (mnemotechnická pomůcka: little end first) a patří mezi ně MOS Technology 6502, Intel x86 a DEC VAX.

Little endian má jednu dobrou vlastnost. Jedna a ta samá hodnota může být z paměti načtena pro různou délku, bez změny adresy. Například 32 bitový řetězec FF 00 00 00 může být načten ze stejné adresy jako 8 bitový (hodnota = FF), 16bitový (00FF), 24bitový (0000FF), 32bitový (000000FF); jejich hodnota stále zůstává 255. Tato vlastnost je však velmi zřídka využívána programátory, kteří pracují s vyššími programovacími jazyky, proto se ponechává kompilátoru.

Např. 32bitové číslo 0x4A3B2C1D se na adresu 100 uloží takto:

100 101 102 103
... 1D 2C 3B 4A ...

Big-endian[editovat | editovat zdroj]

V tomto případě se na paměťové místo s nejnižší adresou uloží nejvíce významný bajt (MSB) a za něj se ukládají ostatní bajty až po nejméně významný bajt (LSB) na konci. Architektury uplatňující tento princip se nazývají big-endian (mnemotechnická pomůcka: big end first) a patří mezi ně Motorola 68000, SPARC a System/370.

Např. 32bitové číslo 0x4A3B2C1D se na adresu 100 uloží takto:

100 101 102 103
... 4A 3B 2C 1D ...

Middle-endian[editovat | editovat zdroj]

Některé architektury označované middle-endian (nebo někdy mixed-endian) užívají složitější způsob pro určení pořadí jednotlivých bajtů, který je dán kombinací obou výše zmíněných způsobů. Mezi takovéto architektury patří např. rodina procesorů PDP-11. Tento formát je také použit pro ukládání čísel s pohyblivou řádovou čárkou a dvojitou přesností v systémech VAX a ARM.

Např. 32bitové číslo 0x4A3B2C1D se na adresu 100 uloží takto:

100 101 102 103
... 3B 4A 1D 2C ...

nebo případně:

100 101 102 103
... 2C 1D 4A 3B ...

Endianity v souborech[editovat | editovat zdroj]

Pokud je binární soubor vytvořen a následně čten na počítačích, které mají různou endianitu, může vzniknout problém. Některé překladače mají vestavěná zařízení, která pracují s údaji zapsanými v jiných formátech. Například kompilátor Intel Fortran podporuje nestandardní CONVERT specifikátor, takže soubor lze otevřít jako:

OPEN (unit, CONVERT = 'BIG_ENDIAN',...)

nebo

OPEN (unit, CONVERT = 'LITTLE_ENDIAN',...)

Pokud kompilátor převod nepodporuje, musí záměnu bajtů (angl. byte swap) provést programátor. Neformátované sekvenční soubory v jazyce Fortran vytvořené s jednou endianitou obvykle není možné číst na systému pomocí jiné endianity. Fortran obvykle provádí záznam (napsán jediným příkazem Fortranu) jako data a pole. Ta jsou rovna celočíselným bytům v datech. Pokus o čtení těchto souborů v systému s jinými endianitami má pak za následek provozní chybu, protože pole počítače jsou nesprávná. Tomuto problému se lze vyhnout tím, že píšeme přímo do sekvenčního binárního souboru.

Aplikace formátující binární data, jako je například MATLAB .mat soubory, nebo formát dat BIL, používané v topografii, jsou obvykle nezávislé na endianitách.

Tohoto je dosaženo uložením dat vždy v jedné pevné endianitě nebo když spolu s údaji neseme příznak, který určí, s kterou endianitou byla data zapsána. Při čtení souboru převede aplikace endianity, je-li třeba. Tento postup je pro uživatele transparentní.

To je případ obrazových souborů TIFF, které informují ve svém záhlaví o endianitě použitých čísel. Pokud soubor začíná signaturou "MM", znamená to, že celá čísla jsou reprezentována jako velký endian, zatímco "II" endian malý. Každá z těchto signatur zabere jediné 16bitové slovo. Jsou typu palindrom (to znamená, že se čtou stejně dopředu i dozadu), takže jsou nezávislé endianitách. "I" znamená Intel a "M" znamená Motorola. Procesory Intel používají malý endian, zatímco procesory Motorola 680x0 velký endian. Tento explicitní podpis umožňuje čtecímu programu obrazových souborů TIFF výměnu bytů, a to pouze v případě, byl-li daný soubor vygenerován zapisovacím programem TIFF běžícím na počítači s jinou endianitou.

I když je programovací prostředí LabVIEW nejčastěji instalováno na počítačích s operačním systémem Windows, bylo nejprve vyvinuto pro Macintosh. Formát velkého endianu je používán pro binární čísla, zatímco většina Windows používá malý endian.

Za povšimnutí stojí fakt, že neexistuje obecný nástroj pro přeměnu endianit v souborech. Ke správnému převodu nutno znát strukturu souboru. Potřebná výměna bajtů závisí totiž na délce proměnných uložených v souboru (čtyřbajtové celé číslo vyžaduje jiný převod než dvojice dvoubajtových celých čísel).

Programování[editovat | editovat zdroj]

Instrukce BSWAP[editovat | editovat zdroj]

Strojová instrukce bswap[1] slouží ke konverzi 32bitových hodnot z little-endian na big-endian a naopak. Tato instrukce je k dispozici pouze na platformách Intel počínaje řadou 80486 (včetně) dál (486+). Zápis (použití) je následující:

BSWAP reg32
BSWAP reg64

Bere buď 32bitový nebo 64bitový registr. Pokud by se použil se 16bitovým registrem, zanechá jej beze změny a nic se neprovede. Pro 16bitový operand a změnu endianity lze na procesorech x86 provést instrukce XCHG, např:

XCHG al, ah

GCC[editovat | editovat zdroj]

Tuto instrukci je možno použít nepřímo voláním speciálního konstruktu překladače GCC (pro jazyk C/C++) od verze 4.3. Jedná se o vestavěnou (built-in) funkci, proto není potřeba vkládat žádný hlavičkový soubor, ani přilinkovat žádnou knihovnu (GCC místo nich vkládá přímo instrukce). Výhodou těchto funkcí je, že pokud daná platforma nemá instrukce pro bitovou konverzi mezi little-endian a big-endinan, vloží místo nich optimalizovaný kód.

Prototyp pro 32bitový swap[editovat | editovat zdroj]

int32_t __builtin_bswap32 (int32_t x);

Vrací 32bitovou hodnotu, která obsahuje přehozenou hodnotu 32bitové proměnné x (0x11223344 → 0x44332211).

Prototyp pro 64bitový swap[editovat | editovat zdroj]

int64_t __builtin_bswap64 (int64_t x);

Podobně jako __builtin_bswap32, ale pracuje s 64bitovými hodnotami (jak parametr, tak i návratová hodnota).

Pro 16bitovou konverzi neexistuje vestavěná (built-in) funkce, ale jelikož se jedná o pouhé prohození dvou bajtů, stačí použít rotaci (viz výpočet níže).

Konverze mezi little-endian a big-endian (jazyk C)[editovat | editovat zdroj]

Následující výpočty přehazují bajty z kódování little-endian na big-endian a naopak. Pokud je to možné, měly by se upřednostňovat strojové instrukce, protože je jejich zpracování rychlejší. Následující výpočty jsou na druhou stranu portabilní. Syntaxe zápisu je provedena na způsob maker v jazyce C.

16bitový swap[editovat | editovat zdroj]

#define BSWAP16(n) ((n) << 8 | ((n) >> 8 & 0x00FF))

32bitový swap[editovat | editovat zdroj]

#define BSWAP32(n) ((n) >> 24) | (((n) << 8) & 0x00FF0000) | (((n) >> 8) & 0x0000FF00) | ((n) << 24)

64bitový swap[editovat | editovat zdroj]

#define BSWAP64(n) ((n) >> 56) | (((n) << 40) & 0x00FF000000000000) | \
                                 (((n) << 24) & 0x0000FF0000000000) | \
                                 (((n) << 8)  & 0x000000FF00000000) | \
                                 (((n) >> 8)  & 0x00000000FF000000) | \
                                 (((n) >> 24) & 0x0000000000FF0000) | \
                                 (((n) >> 40) & 0x000000000000FF00) | \
                                 ((n) << 56)

Použití struktury union[editovat | editovat zdroj]

Následujícím způsobem lze přehazovat bajty libovolně dlouhého datového typu. Daň, kterou si tato obecnost bere, je rychlost. Proto je daleko vhodnější použít některou z výše uvedených možností (instrukce, výpočet). Další problém, který může nastat, je, že musíme zajistit přesný počet bajtů daného datového typu. Není definované, že např. int musí mít přesný počet bitů (je definována pouze jeho dolní hranice).[2][3]

int32_t BSWAP32(int32_t data)
{
    int i, i2, tmp;
    union {
        int32_t val;
        uint8_t bytes[sizeof(int32_t)];
    } lf;
    lf.val = data;
    for(i = 0, i2 = sizeof(int32_t) -1; i < sizeof(int32_t) / 2; i++, i2--)
    {
        tmp = lf.bytes[i];
        lf.bytes[i] = lf.bytes[i2];
        lf.bytes[i2] = tmp;
    }
    return lf.val;
}

Visual C++[editovat | editovat zdroj]

V jazyce C++ je potřeba vložit hlavičkový soubor intrin.h.

16bitový swap[editovat | editovat zdroj]

unsigned short _byteswap_ushort(unsigned short value);

32bitový swap[editovat | editovat zdroj]

unsigned long _byteswap_ulong(unsigned long value);

64bitový swap[editovat | editovat zdroj]

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

Detekce[editovat | editovat zdroj]

Detekci endianity je možné provést jak při překladu, tak při spuštění programu.

Při překladu (Unix)[editovat | editovat zdroj]

Detekce při překladu by měla být preferována, protože se provede pouze jednou a výsledný program je tak jako tak závislý na dané platformě, kde byl překompilován. Ve většině unixových systémů je k dispozici hlavičkový soubor sys/param.h, který kromě jiného poskytuje informace o pořadí bajtů (byte order). Definuje makra __BYTE_ORDER, __LITTLE_ENDIAN, __BIG_ENDIAN, a další, která lze využít např. následovně:

#include <sys/param.h>
 
#ifdef __BYTE_ORDER
# if __BYTE_ORDER == __LITTLE_ENDIAN
# define ENDIAN_DEFAULT ENDIAN_LITTLE
# elif __BYTE_ORDER == __BIG_ENDIAN
# define ENDIAN_DEFAULT ENDIAN_BIG
# else
# error "Unknown byte order" /* Middle-endian? */
# endif
#endif /* __BYTE_ORDER */

Dalším možným přístupem je rozhodnutí na základě znalosti endianity příslušné platformy. To může být komplikované, protože se jména maker (symbolických konstant) mohou lišit v závislosti na použitém překladači a zároveň nemusí být možné podchytit všechny platformy (včetně hybridních architektur).

#if defined (i386) || defined (__i386__) || defined (__alpha) || defined (vax)
# define ENDIAN_DEFAULT ENDIAN_LITTLE
#else
# define ENDIAN_DEFAULT ENDIAN_BIG
#endif

Za běhu[editovat | editovat zdroj]

Pokud nejsou k dispozici žádná makra, je možné použít výpočet za běhu programu. Obě následující funkce vrací 1, když je platforma big-endian, jinak vrací 0.

int is_big_endian()
{
  static const int i = 1;
  return *((char *)&i) == 0;
}

Ve výše uvedeném kódu je do proměnné i (která má 32 bitů)[3] uložena hodnota 1. V závislosti na dané platformě (endianitě), se číslo uloží bud jako 0x00000001 (big-endian) nebo jako 0x01000000 (little-endian) a pouze se kontroluje, jakou hodnotu obsahuje první bajt proměnné i.

Použití struktury union (Harbison and Steele)[editovat | editovat zdroj]

Příklad použití struktury union:[4] (Tento kód jsem výrazně zefektivnil oproti originálu – Miloslav Ponkrác)

int is_big_endian()
{
  union test_union
  {
    int i;
    char c;
  };
 
  static const union test_union s_t = { 1 };
  return s_t.c == 0;
}

Reference[editovat | editovat zdroj]

  1. Intel Instruction Set (bswap)
  2. Integer computer science
  3. a b limits.h
  4. C: A Reference Manual. Samuel P. Harbison and Guy L. Steele, Jr. Third edition; Prentice-Hall, 1991, ISBN 0-13-110933-2

Externí odkazy[editovat | editovat zdroj]