If each memory address can hold 1 byte (8 bits) of data
Non è esattamente giusto - non è che la memoria è suddivisa in una serie di scatole, ognuna delle quali ha una dimensione di 1 byte; è che 1 byte è la più piccola unità di memoria indirizzabile . È possibile indirizzare la memoria con incrementi maggiori.
Quindi, se hai un puntatore char *p
contenente un indirizzo come 0x12345670
, questo è essenzialmente solo un offset in memoria: punta a l'inizio di una regione che può comprendere un singolo byte o più byte, come un numero intero, un array o una struttura. (In effetti è leggermente più complicato, dal momento che ciò che vedete come uno spazio di indirizzamento piatto è in realtà memoria virtuale che è mappato sulla memoria fisica dal kernel del sistema operativo, ma ai fini di questa spiegazione non lo fa fare la differenza.)
Un intero a 32 bit con valore 0xAABBCCDD
all'indirizzo p
occupa semplicemente 4 byte. Questi byte possono essere disposti dalla CPU nell'ordine big-endian , dove i più bit significativi sono memorizzati all'indirizzo più basso:
0x1234566F …
0x12345670 0xAA
0x12345671 0xBB
0x12345672 0xCC
0x12345673 0xDD
0x12345674 …
O little-endian , dove i bit significativi minimi sono memorizzati all'indirizzo più basso:
0x1234566F …
0x12345670 0xDD
0x12345671 0xCC
0x12345672 0xBB
0x12345673 0xAA
0x12345674 …
Un linguaggio di programmazione come C riassume in qualche modo questo per fornire un modo conveniente per indirizzare oggetti di dimensioni diverse. Supponiamo che ci sia una serie di interi a 32 bit a
, e p
è l'indirizzo del primo elemento: p = &a[0]
. In assembly, se desideri eseguire iterazioni su questo array, devi incrementare p
per 4 ogni volta per spostarlo al numero intero successivo:
&a[0] == p
&a[1] == p + 4
&a[2] == p + 8
&a[3] == p + 12
&a[4] == p + 16
…
In C, un'espressione come p + 1
non aggiunge solo un numero di byte al valore di p
, ma aggiunge multipli della dimensione dell'oggetto, sizeof(*p)
-so se p
è stato digitato come uint32_t *p
, quindi a[1]
sarebbe a p + 1
, a[2]
a p + 2
e così via. Sotto il cofano, p + n
diventa qualcosa come (char *)p + n * sizeof(*p)
.
how does the CPU know when to stop reading bits of the memory addresses?
Un tipo primitivo come un intero è sempre una dimensione fissa. Quando scrivi *pi += 42
per aggiungere 42
al contenuto del numero intero a 32 bit a cui fa riferimento pi
, questo viene tradotto specificamente in un'istruzione di aggiunta indiretta a 32 bit. Un tipo composto come un array è solo una serie di valori agli indirizzi che sono multipli della dimensione dell'oggetto: il tuo programma è responsabile dell'accesso solo all'interno dei limiti dell'array. Le lingue di livello superiore inseriscono controlli automatici in fase di runtime o in fase di compilazione per garantire sicurezza della memoria impedendo, tra le altre cose, accessi non validi all'array.
Un valore allocato dinamicamente come il risultato di malloc
è solo una regione di memoria che l'allocatore ti ha dato il controllo, che a sua volta ottiene dal sistema operativo. Puoi lanciarlo su qualunque tipo tu voglia, come una serie di strutture personalizzate, purché tu acceda solo all'interno della regione che ti è stata concessa dall'allocatore.