Memoria allocata senza allocazione usando malloc, come?

Di seguito è riportato un semplice frammento di codice:

int main() { int *p; p=(int*)malloc(sizeof(int));//allocate m/y 4 1 int printf("P=%p\tQ=%p",p,p+2); } 

In un esempio di esecuzione, mi ha dato l’output come di seguito:

 P=0x8210008 Q=0x8210010 

L’indirizzo iniziale di P è-P = 0x8210008, il byte successivo è 0x8210009, il byte successivo è 0x821000A, il byte successivo è 0x821000B. Quindi i 4 byte per int si concludono lì. Non abbiamo assegnato più memoria usando malloc. Quindi, come p + 2 ci porta a 0x8210010, che è 8 byte dopo P (0x8210008).

Innanzitutto, il fatto che tu abbia stampato un indirizzo non implica che la memoria sia allocata a quell’indirizzo. Hai semplicemente aggiunto numeri e prodotto altri numeri.

In secondo luogo, il motivo per cui il numero ottenuto con l’aggiunta di due era otto maggiore dell’indirizzo di base anziché due maggiore dell’indirizzo di base era perché, quando si aggiungono numeri interi ai puntatori in C, l’aritmetica viene eseguita in termini di elementi puntati , non in termini di byte in memoria (a meno che gli elementi puntati siano byte). Supponiamo che tu abbia una matrice di int, ad esempio int x[8] , e che tu abbia un puntatore a x[3] . L’aggiunta di due a quel puntatore produce un puntatore a x[5] , non un puntatore a due byte oltre l’inizio di x[3] . È importante ricordare che C è un’astrazione e lo standard C specifica cosa succede all’interno di questa astrazione. All’interno dell’astrazione C, l’aritmetica del puntatore funziona su numeri di elementi, non su indirizzi di memoria grezzi. L’implementazione C (il compilatore e gli strumenti che trasformano il codice C nell’esecuzione del programma) è necessaria per eseguire qualsiasi operazione sugli indirizzi di memoria grezzi necessari per implementare l’astrazione specificata dallo standard C. In genere, ciò significa che il compilatore moltiplica un numero intero in base alla dimensione di un elemento quando lo si aggiunge a un puntatore. Quindi due è moltiplicato per quattro (su una macchina in cui un int è quattro byte) e l’otto risultati viene aggiunto all’indirizzo di base.

Terzo, non puoi fare affidamento su questo comportamento. Lo standard C definisce l’aritmetica del puntatore solo per i puntatori che puntano agli oggetti all’interno di matrici, incluso un object fittizio alla fine dell’array. Inoltre, i puntatori ai singoli oggetti si comportano come matrici di un elemento. Quindi, se si ha un puntatore p che punta ad un int, si è autorizzati a calcolare p+0 o p+1 , perché puntano all’unico object nell’array ( p+0 ) e all’object fittizio uno oltre l’ultimo elemento nella matrice ( p+1 ). Non sei autorizzato a calcolare p-1 o p+2 , perché questi sono al di fuori dell’array. Nota che non si tratta di dereferenziare il puntatore (tentativo di leggere o scrivere memoria all’indirizzo calcolato): anche il semplice calcolo dell’indirizzo comporta un comportamento non definito dallo standard C: il tuo programma potrebbe bloccarsi, potrebbe darti Risultati “corretti”, o potrebbe cancellare tutti i file nel tuo account e tutti questi comportamenti sarebbero conformi allo standard C.

È improbabile che il solo calcolo di un indirizzo fuori limite possa produrre un comportamento così strano. Tuttavia, lo standard lo consente perché alcuni processori di computer hanno schemi di indirizzi insoliti che richiedono più lavoro rispetto alla semplice aritmetica. Forse lo schema di indirizzo secondo più comune dopo lo spazio di indirizzamento piatto è un indirizzo di base e uno schema di offset. In tale schema, i 16 bit alti di un puntatore a quattro byte potrebbero contenere un indirizzo di base e i 16 bit bassi potrebbero contenere un offset. Per un dato indirizzo di base b e offset o, l’indirizzo virtuale corrispondente potrebbe essere 4096 * b + o. (Tale schema è in grado di indirizzare solo 2 byte e molti valori diversi di base e offset possono riferirsi allo stesso indirizzo. Ad esempio, base 0 e offset 4096 si riferiscono allo stesso indirizzo di base 1 e offset 0.) Con uno schema base-offset, il compilatore potrebbe implementare l’aritmetica del puntatore aggiungendo solo all’offset e ignorando la base. (Una tale implementazione C può supportare matrici solo fino a 65536 byte, l’estensione indirizzabile solo dall’offset.) In tale implementazione, se si ha il puntatore a int p con una codifica di 0x0000fffc (base 0, offset 65532), e int è quattro byte, quindi p+2 avrà il valore 0x00000004, non il valore maggiore di otto (0x00010004).

Questo è un esempio in cui l’aritmetica del puntatore produce valori che non ci si aspetterebbe da una macchina con indirizzo flat. È più difficile immaginare un’implementazione in cui l’aritmetica dei puntatori che non è valida secondo lo standard C produrrebbe un arresto anomalo. Tuttavia, si consideri un’implementazione in cui la memoria deve essere scambiata manualmente da un processo, perché il processore non ha l’hardware per supportare la memoria virtuale. In tale implementazione, i puntatori potrebbero contenere indirizzi di strutture in memoria che descrivono posizioni del disco e altre informazioni utilizzate per gestire lo scambio di memoria. In una tale implementazione, fare l’aritmetica del puntatore potrebbe richiedere la lettura delle strutture in memoria, e così facendo l’aritmetica del puntatore non valida potrebbe leggere indirizzi non validi.

Perché lo tratta come offset di un elemento intero dal puntatore. Hai assegnato un array per un singolo intero. Quando chiedi p+2 è uguale a &p[2] . Se vuoi due byte dall’inizio, devi prima lanciarlo su char* :

 char *highWordAddr = (char*)p + 2; 

C è felice di lasciarti fare qualsiasi aritmetica puntatore che ti piace. Solo perché p+2 assomiglia a qualsiasi altro indirizzo non significa che sia valido. In effetti, in questo caso, non lo è.

Fai molta attenzione ogni volta che vedi l’aritmetica del puntatore che non stai andando oltre i limiti assegnati.