Endianita

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

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

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, Apple M1[1] 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 32bitový řetězec FF 00 00 00 může být načten ze stejné adresy jako 8bitový (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:

100101102103
...1D2C3B4A...

Big-endian

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:

100101102103
...4A3B2C1D...

Middle-endian

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:

100101102103
...3B4A1D2C...

nebo případně:

100101102103
...2C1D4A3B...

Endianity v souborech

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í

Konverze mezi little-endian a big-endian (jazyk C)

Následující výpočty přehazují bajty z kódování little-endian na big-endian a naopak. Použití standardního C-jazyka vede k lepší přenositelnosti kódu. Zpracování pomocí strojových instrukcí je rychlejší, což může hrát roli při zpracování velkých souborů dat, nebo velkých datových toků. Syntaxe zápisu je provedena na způsob maker v jazyce C.

Aby se konverze provedla správně, musíme vědět jaký počet bajtů používá daný kompilátor C-jazyka pro určitý datový typ. Definice C-jazyka podle ANSI neurčuje, že např. typ int musí mít 32 bitů.

16bitový swap

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

32bitový swap

#define BSWAP32(n) (((n) & 0xFF000000L >> 24) | ((n) & 0x00FF0000L >> 8) | ((n) & 0x0000FF00L << 8) | ((n) & 0x000000FF << 24))

64bitový swap

#define BSWAP64(n) ((n) >> 56) | (((n) << 40) & 0x00FF000000000000LL) | \
                                 (((n) << 24) & 0x0000FF0000000000LL) | \
                                 (((n) << 8)  & 0x000000FF00000000LL) | \
                                 (((n) >> 8)  & 0x00000000FF000000LL) | \
                                 (((n) >> 24) & 0x0000000000FF0000LL) | \
                                 (((n) >> 40) & 0x000000000000FF00LL) | \
                                 ((n) << 56)

Použití struktury union

Následující kód demonstruje "přehazování" bajtů 32bitového datového typu. Obecně jde o poměrně neefektivní způsob konverze endianity, ale např. při smíšené endianitě by může být názornost tohoto postupu výhodou.

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;
}

Architektura 80486 a odvozené

Tato instrukce je k dispozici pouze na platformách Intel počínaje řadou 80486 (včetně) dál (486+) existuje pro konverzi endianity strojová instrukce bswap[2]. Tato instrukce slouží ke konverzi 32bitových nebo 64bitových hodnot z little-endian na big-endian a naopak. 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

Použití instrukce BSWAP v GCC

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
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
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 v GCC vestavěná (built-in) funkce.

Visual C++

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

16bitový swap

unsigned short _byteswap_ushort(unsigned short value);

32bitový swap

unsigned long _byteswap_ulong(unsigned long value);

64bitový swap

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

Detekce

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

Při překladu (Unix)

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

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 (u které předpokládáme délku 32 bitů) uložena hodnota 1. V závislosti na endianitě dané platformy, se číslo uloží buď jako posloupnost bajtů {0x00,0x00,0x00,0x01} (big-endian) nebo jako posloupnost bajtů {0x01,0x00,0x00,x00} (little-endian). Funkce is_big_endian pouze kontroluje, jakou hodnotu obsahuje první bajt proměnné i.

Použití struktury union (Harbison and Steele)

Příklad použití struktury union:[3]

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

  1. Porting Your macOS Apps to Apple Silicon. Apple Developer Documentation [online]. [cit. 2023-12-19]. Dostupné online. (anglicky) 
  2. Intel Instruction Set (bswap)
  3. C: A Reference Manual. Samuel P. Harbison and Guy L. Steele, Jr. Third edition; Prentice-Hall, 1991, ISBN 0-13-110933-2

Externí odkazy