Il contenuto di un indice dell'array è memorizzato in un indirizzo di memoria?

2

Assumi in C il seguente array di un elemento:

int a[] = {2000};

2000 in binario sarebbe:

11111010000

Se ciascun indirizzo di memoria può contenere 1 byte (8 bit) di dati, quindi come in alcuni tutorial, si dice che ogni indice di array è memorizzato in un indirizzo di memoria? non è possibile dal momento che 2000 ha 11 bit e necessita di almeno 2 indirizzi di memoria da memorizzare.

La mia seconda domanda è, se è memorizzata in 2 (o più) indirizzi di memoria, come fa la CPU a sapere quando smettere di leggere i bit degli indirizzi di memoria? come fa a sapere che ha raggiunto la fine della variabile a sopra?

    
posta Joseph a 06.09.2018 - 02:33
fonte

4 risposte

5

Considera il seguente programma:

#include <stdio.h>

int main(int argc, char* argv[]) {
    int a[] = {2000, 3000};
    printf("%p %p\n", &a[0], &a[1]);
    return 0;
}

Stampa gli indirizzi degli elementi dell'array. Per una corsa sulla mia macchina:

0x7ffc963400c0 0x7ffc963400c4

Notate che sono 4 a parte. Per una matrice int , ogni indice dell'array salta sizeof(int) indirizzi di memoria. Questo è lo stesso per tutti i tipi di array, inclusi gli array di structs . L'indice dell'array identifica l'elemento , non il byte .

    
risposta data 06.09.2018 - 03:07
fonte
1

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.

    
risposta data 06.09.2018 - 04:10
fonte
1

If each memory address can hold 1 byte (8 bits) of data, then how come in some tutorials, it is mentioned that each array index is stored in one memory address? it's not possible since 2000 has 11 bits and needs at least 2 memory addresses to be stored.

Sì, ogni indirizzo di memoria può contenere un byte. Tuttavia, spesso viene utilizzato un blocco di memoria per archiviare le cose - questo significa usare posizioni di memoria in corrispondenza di indirizzi di memoria consecutivi per memorizzare qualcosa di più grande di un byte. Un int è spesso lungo 4 byte, ad esempio; tuttavia, possiamo anche memorizzare le strutture e gli oggetti che sono ancora più grandi, per non parlare degli array.

My second question is, if it is stored in 2 (or more) memory addresses, how does the CPU know when to stop reading bits of the memory addresses? how does it know it has reached the end of the variable a above?

Le CPU supportano più tipi di dati in quanto: hanno codifiche di istruzioni per carico di byte, carico di parole e carico di parole lunghe e un carico in virgola mobile. Quando si tratta di oggetti più grandi come una struttura o un oggetto, il compilatore genererà più istruzioni (forse in linea, forse in un ciclo, o forse chiamando una funzione di supporto nella libreria, ad esempio qualcosa come memmove per copiare una struttura più grande ).

La CPU semplicemente fa ciò che viene istruito dal codice generato dal compilatore. Il compilatore sceglie la posizione di memoria & layout per variabili. Successivamente, il compilatore genera istruzioni per accedere a tali posizioni di archiviazione.

Si noti che tutta la CPU fa eseguire le istruzioni programmate (del compilatore o dell'assieme), che spiegano come operare su quali dati in piccoli passi - la CPU non conosce la posizione e l'ampli; layout di variabili di per sé, esegue solo piccole istruzioni (molto velocemente).

Quindi, è compito del compilatore generare flussi di istruzioni coerenti per la CPU che siano fedeli al significato del codice sorgente. La definizione della lingua (ad esempio lo standard C) indica al compilatore (o al compilatore del compilatore) il significato dei costrutti del linguaggio trovati nel codice sorgente e questo guida la traduzione al codice macchina.

    
risposta data 06.09.2018 - 14:26
fonte
0

@Josepha ha detto "ok ma non è UN indirizzo di memoria solo un byte?".

Non in C. Il linguaggio C definisce una macchina astratta che è una sovrapposizione sull'architettura hardware. Il compilatore fa in modo che le cose appaiano come se steste correndo sulla macchina astratta, ma emettendo il codice necessario per quella specifica architettura. Non c'è spazio di indirizzo virtuale o fisico in C, solo i puntatori agli oggetti e puntatore a vuoto, specificato nello standard. Quando si lavora con un puntatore in C, si ha un token che rappresenta una posizione di memoria, non un indirizzo di memoria. Quel valore del puntatore, in realtà potrebbe avere un modello di bit che corrisponde a quello di un pattern di bit del bus indirizzo, ma non è rilevante qui.

Quando hai un puntatore a un int, punta a un oggetto che ha sizeof (int), non la dimensione della cella di memoria fisica.

    
risposta data 06.09.2018 - 03:02
fonte

Leggi altre domande sui tag