TakéPaměťová adresa, Adresa paměťové buňky, Memory addressPokročilý

Definice

Adresa v paměti je číslo, kterým procesor jednoznačně označuje konkrétní buňku operační paměti, obvykle jeden bajt. Program pomocí adresy říká, odkud má data načíst nebo kam je zapsat. Adresy tvoří souvislý číselný rozsah, jehož velikost určuje šířka adresní sběrnice a architektura, typicky 32 nebo 64 bitů.

Kategorie: Počítačová architekturaAktualizováno

Nezaměňujte: V počítačové architektuře jde o číslo buňky operační paměti, zatímco v sítích se slovem „adresa“ myslí IP nebo MAC adresa zařízení.

Co adresa v paměti vlastně označuje

Adresa v paměti je pořadové číslo bajtu v adresním prostoru. Paměť si lze představit jako velmi dlouhou řadu očíslovaných přihrádek: každá pojme osm bitů a každá má vlastní číslo od nuly nahoru. Když procesor vykonává instrukci „načti hodnotu“, nese s sebou právě takové číslo. Adresy se běžně zapisují šestnáctkově, například 0x7ffd3a1b40, protože hexadecimální zápis přehledně kopíruje bitovou strukturu.

Proč jsou adresy dnes 64bitové

Šířka adresy určuje, kolik různých buněk lze očíslovat. Třicetidvoubitová adresa pokryje 2^32 bajtů, tedy 4 GiB, což byl u desktopů dlouho strop. Šedesátičtyřbitová adresa dává rozsah, který žádný existující stroj nezaplní, takže procesory reálně dekódují jen část bitů (typicky 48 nebo 57). Zbylé horní bity musí být kopií nejvyššího použitého bitu, jinak procesor adresu odmítne jako neplatnou.

Virtuální adresa versus fyzická

Adresa, kterou vidí váš program, skoro nikdy neodpovídá pozici v čipu RAM. Operační systém dává každému procesu vlastní virtuální adresní prostor a jednotka MMU v CPU ho po stránkách (obvykle 4 KiB) překládá na fyzické adresy pomocí stránkových tabulek. Díky tomu se dva procesy nemohou navzájem přepsat, i když oba pracují s číslem 0x1000. Překlad má cenu: každý přístup navíc vyžaduje dohledání v tabulce, což zrychluje vyrovnávací paměť TLB.

Zarovnání

Čtyřbajtový int chce ležet na adrese dělitelné čtyřmi, osmibajtový double na adrese dělitelné osmi. Nezarovnaný přístup je na některých architekturách pomalejší a na jiných rovnou chyba. Proto překladač vkládá mezi položky struktury výplňové bajty a velikost struktury bývá větší než součet jejích částí.

Ukazatel není totéž co adresa

Ukazatel je proměnná, která adresu drží, plus typová informace o tom, co na dané adrese leží. Právě typ určuje, kolik bajtů se přečte a jak se interpretují a o kolik se adresa posune při aritmetice nad ukazatelem. Jazyky jako Python nebo Java adresy před programátorem schovávají za reference, ale pod kapotou pracují stejně.

Kde se adresy stávají bezpečnostním tématem

Znalost konkrétní adresy je pro útočníka polovina úspěchu: klasické zneužití přetečení bufferu potřebuje vědět, kam skočit. Obranou je ASLR, tedy náhodné rozmístění zásobníku, haldy a knihoven při každém spuštění, takže se adresy mezi běhy liší. Únik jediné platné adresy do logu nebo chybové hlášky proto bývá považován za zranitelnost, i když sám o sobě nic nepřepíše.

Speciální hodnoty

Adresa 0 je vyhrazená: značí „nikam“ a operační systém ji záměrně nechává nenamapovanou, aby dereference nulového ukazatele okamžitě spadla místo tichého poškození dat. Vedle běžné paměti existují také adresy mapované na hardware (memory-mapped I/O), kde zápis na určité číslo nepíše do paměti, ale rozsvítí LED nebo odešle bajt na sériovou linku.

Příklady z praxe

  1. Dva procesy, stejné číslo, jiná data

    Spustíte tentýž program dvakrát a v obou instancích si vypíšete adresu lokální proměnné. Bez zapnutého ASLR mohou obě vypsat identickou hodnotu, přesto se jejich data nikde nepotkají: virtuální adresy jsou v každém procesu přeloženy na jiné fyzické stránky. Právě proto nemá smysl posílat holou adresu mezi procesy, sdílená paměť se musí namapovat explicitně.

    #include <stdio.h>
    int main(void) {
        int x = 42;
        printf("hodnota %d, adresa %p\n", x, (void*)&x);
        return 0;
    }
  2. Výplň ve struktuře kvůli zarovnání

    Struktura s jedním bajtem a jedním čtyřbajtovým intem nezabere pět bajtů, ale osm. Překladač za char doplní tři výplňové bajty, aby int začínal na adrese dělitelné čtyřmi. Přeuspořádání položek od největší k nejmenší dokáže u velkých polí struktur ušetřit desítky procent paměti.

    struct Bad  { char c; int n; };      // sizeof == 8
    struct Good { int n; char c; char d; }; // sizeof == 8, ale nese o hodnotu navic

Časté omyly

MýtusKdyž si vypíšu adresu proměnné, vidím, kde přesně leží v RAM.
Ve skutečnostiVýpis ukazuje virtuální adresu procesu. Skutečnou fyzickou pozici zná jen jádro a stránkovací jednotka a ta se navíc může za běhu změnit, například při odložení stránky na disk.
Mýtus64bitový procesor umí adresovat 16 exbibajtů paměti.
Ve skutečnostiTeoretický rozsah 2^64 sice existuje, ale běžné implementace x86-64 dekódují jen 48 nebo 57 nejnižších bitů. Zbytek adresy musí mít předepsaný tvar, jinak jde o neplatnou adresu a přístup skončí výjimkou.
MýtusUkazatel a adresa jsou totéž.
Ve skutečnostiAdresa je samotné číslo, ukazatel je typovaná proměnná, která toto číslo obsahuje. Typ rozhoduje o počtu čtených bajtů i o kroku ukazatelové aritmetiky, takže posun o jedničku znamená u int čtyři bajty, u char jeden.

Časté dotazy

Proč se adresa v paměti zapisuje šestnáctkově?
Adresa v paměti se zapisuje šestnáctkově proto, že jedna hexadecimální číslice odpovídá přesně čtyřem bitům. Osmimístné hexadecimální číslo tak reprezentuje 32bitovou adresu bez zbytku a hranice stránek či zarovnání jsou v takovém zápisu vidět na první pohled: adresa končící nulami je zarovnaná na mocninu dvou. Desítkový zápis by tuto strukturu zamaskoval a binární by byl pro člověka nečitelně dlouhý. Ladicí nástroje, disassemblery i výpisy jádra proto používají hexadecimální formát jednotně.
Mění se adresa proměnné mezi jednotlivými spuštěními programu?
Adresy se mezi spuštěními zpravidla mění, protože moderní operační systémy používají ASLR, tedy náhodné rozmístění adresního prostoru. Zásobník, halda i sdílené knihovny dostanou při každém startu jiný základ, takže vypsaná adresa lokální proměnné bude pokaždé odlišná. Cílem je ztížit útoky, které potřebují předem znát cílovou adresu. Vypnout ASLR lze pro účely ladění, ale spoléhat na konkrétní číselnou hodnotu adresy v produkčním kódu je vždy chyba.
K čemu je adresa v paměti dobrá programátorovi v jazyce bez ukazatelů?
I v jazycích jako Java, C# nebo Python pomáhá znalost adres pochopit chování programu. Reference jsou pod kapotou adresy, takže porovnání dvou objektů operátorem identity srovnává právě jejich umístění v paměti. Stejně tak vysvětluje, proč změna objektu předaného do funkce je venku vidět, proč velké kolekce zatěžují cache a proč je sekvenční průchod polem rychlejší než skákání po zřetězeném seznamu. Adresy tedy nezmizely, jen je runtime spravuje za vás.
Co se stane při přístupu na neplatnou adresu v paměti?
Přístup na adresu, která není v procesu namapovaná, vyvolá stránkovou výjimku. Jádro zjistí, že pro danou virtuální adresu neexistuje platný záznam ve stránkových tabulkách, a proces ukončí signálem SIGSEGV, na Windows chybou access violation. Typickou příčinou je dereference nulového nebo již uvolněného ukazatele, čtení za koncem pole nebo zápis do oblasti jen pro čtení. Ladicí nástroje jako AddressSanitizer nebo Valgrind takové přístupy odhalí dřív, než dojde k pádu v produkci.

Zdroje

  1. Memory address(otevře se v novém okně)Wikipedia
  2. Virtual Memory(otevře se v novém okně)The Linux Kernel documentation
  3. Pointers and memory(otevře se v novém okně)MDN Web Docs
  4. Virtual address space(otevře se v novém okně)Microsoft Learn

Související pojmy

Potřebujete to vyřešit v praxi?

Poradíme, jak na to ve vašem projektu

Vysvětlit pojem je jedna věc, navrhnout kolem něj funkční řešení druhá. Ozvěte se a probereme, co dává smysl u vás.