Che cos'è esattamente un puntatore C se non un indirizzo di memoria?


206

In una fonte attendibile su C, le seguenti informazioni vengono fornite dopo aver discusso &dell'operatore:

... È un po 'sfortunato che la terminologia [indirizzo di] rimanga, perché confonde coloro che non sanno di cosa trattano gli indirizzi, e induce in errore quelli che lo fanno: pensare ai puntatori come se fossero indirizzi di solito porta al dolore .. .

Altri materiali che ho letto (da fonti altrettanto affidabili, direi) si sono sempre riferiti in modo inequivocabile ai puntatori e &all'operatore come indirizzi di memoria. Mi piacerebbe continuare a cercare l'attualità della questione, ma è un po 'difficile quando fonti attendibili di questo tipo non sono d'accordo.

Ora sono leggermente confuso: cos'è esattamente un puntatore, quindi se non un indirizzo di memoria?

PS

L'autore in seguito dice: ... Continuerò a usare il termine "indirizzo di", perché inventarne uno diverso [termine] sarebbe anche peggio.


118
Un puntatore è una variabile che contiene un indirizzo. Ha anche un proprio indirizzo. Questa è la differenza fondamentale tra un puntatore e un array. Un array è effettivamente un indirizzo (e, di conseguenza, il suo indirizzo è esso stesso ).
WhozCraig

7
Qual è la tua "fonte affidabile" per il preventivo?
Cornstalks

22
La fonte stimabile per eccellenza è lo standard linguistico e non i libri semi-derivati ​​da esso e semi-tirati-dal-autore-del-culo. L'ho imparato nel modo più duro, facendo quasi ogni errore che potevo e costruendo lentamente un modello mentale di C un po 'vicino a quello descritto dallo standard e infine sostituendo quel modello con il modello dello standard.
Alexey Frunze,

9
@thang La gente pensa pointer = integer perché spesso è così (x86 Linux e Windows "ci insegnano"), perché la gente ama generalizzare, perché le persone non conoscono bene lo standard linguistico e perché hanno poca esperienza con radicalmente diversi piattaforme. È probabile che quelle stesse persone presumano che un puntatore a dati e un puntatore a una funzione possano essere convertiti l'uno nell'altro e che i dati possano essere eseguiti come codice e sia possibile accedere al codice come dati. Sebbene ciò possa essere vero per le architetture von Neuman (con 1 spazio indirizzo), ma non necessariamente per le architetture Harvard (con codice e spazi dati).
Alexey Frunze,

6
Gli standard di @exebook non sono per i neofiti (specialmente quelli completi). Non sono tenuti a fornire presentazioni gentili e una moltitudine di esempi. Definiscono formalmente qualcosa, quindi può essere correttamente implementato dai professionisti.
Alexey Frunze,

Risposte:


148

Lo standard C non definisce cosa sia un puntatore internamente e come funzioni internamente. Questo è intenzionale in modo da non limitare il numero di piattaforme, in cui C può essere implementato come linguaggio compilato o interpretato.

Un valore di puntatore può essere una sorta di ID o handle o una combinazione di più ID (saluta segmenti e offset x86) e non necessariamente un vero indirizzo di memoria. Questo ID può essere qualsiasi cosa, anche una stringa di testo di dimensioni fisse. Le rappresentazioni senza indirizzo possono essere particolarmente utili per un interprete C.


34
Non c'è molto da spiegare. Ogni variabile ha il suo indirizzo in memoria. Ma non è necessario memorizzare i loro indirizzi in puntatori a loro. Invece puoi numerare le tue variabili da 1 a qualunque e memorizzare quel numero nel puntatore. Ciò è perfettamente legale per lo standard linguistico fintanto che l'implementazione sa come trasformare quei numeri in indirizzi e come fare l'aritmetica del puntatore con quei numeri e tutte le altre cose richieste dallo standard.
Alexey Frunze,

4
vorrei aggiungere che su x86, un indirizzo di memoria è costituito da un selettore di segmento e un offset, quindi rappresenta un puntatore come segmento: offset sta ancora usando l'indirizzo di memoria.
Grazie

6
@Lundin Non ho problemi a ignorare la natura generica dello standard e quella inapplicabile quando conosco la mia piattaforma e il mio compilatore. La domanda originale è generica, tuttavia, quindi non puoi ignorare lo standard quando rispondi.
Alexey Frunze,

8
@Lundin Non devi essere rivoluzionario o scienziato. Supponiamo di voler emulare un computer a 32 bit su un computer fisico a 16 bit e di estendere i 64 KB di RAM a un massimo di 4 GB utilizzando l'archiviazione su disco e implementando i puntatori a 32 bit come offset in un enorme file. Questi puntatori non sono indirizzi di memoria reali.
Alexey Frunze,

6
Il miglior esempio che io abbia mai visto di questo fu l'implementazione in C di Symbolics Lisp Machines (circa 1990). Ogni oggetto C è stato implementato come un array Lisp e i puntatori sono stati implementati come una coppia di un array e un indice. A causa del controllo dei limiti dell'array di Lisp, non è mai stato possibile traboccare da un oggetto all'altro.
Barmar

62

Non sono sicuro della tua fonte, ma il tipo di linguaggio che stai descrivendo deriva dallo standard C:

6.5.3.2 Operatori di indirizzi e indirizzi indiretti
[...]
3. L'unario e l'operatore fornisce l'indirizzo del proprio operando. [...]

Quindi ... sì, i puntatori indicano gli indirizzi di memoria. Almeno è così che lo standard C suggerisce che significhi.

Per dirlo un po 'più chiaramente, un puntatore è una variabile che contiene il valore di un indirizzo . L'indirizzo di un oggetto (che può essere memorizzato in un puntatore) viene restituito con l' &operatore unario .

Posso memorizzare l'indirizzo "42 Wallaby Way, Sydney" in una variabile (e quella variabile sarebbe una sorta di "puntatore", ma dal momento che non è un indirizzo di memoria non è qualcosa che chiameremmo correttamente un "puntatore"). Il tuo computer ha indirizzi per i suoi secchi di memoria. I puntatori memorizzano il valore di un indirizzo (ovvero un puntatore memorizza il valore "42 Wallaby Way, Sydney", che è un indirizzo).

Modifica: voglio espandere il commento di Alexey Frunze.

Che cos'è esattamente un puntatore? Diamo un'occhiata allo standard C:

6.2.5 Tipi
[...]
20. [...]
Un tipo di puntatore può essere derivato da un tipo di funzione o da un tipo di oggetto, chiamato tipo di riferimento . Un tipo di puntatore descrive un oggetto il cui valore fornisce un riferimento a un'entità del tipo di riferimento. Un tipo di puntatore derivato dal tipo di riferimento T viene talvolta chiamato "puntatore a T". La costruzione di un tipo di puntatore da un tipo di riferimento viene chiamata '' derivazione del tipo di puntatore ''. Un tipo di puntatore è un tipo di oggetto completo.

In sostanza, i puntatori memorizzano un valore che fornisce un riferimento a qualche oggetto o funzione. Tipo. I puntatori hanno lo scopo di memorizzare un valore che fornisce un riferimento a qualche oggetto o funzione, ma non è sempre così:

6.3.2.3 Puntatori
[...]
5. Un numero intero può essere convertito in qualsiasi tipo di puntatore. Ad eccezione di quanto precedentemente specificato, il risultato è definito dall'implementazione, potrebbe non essere allineato correttamente, potrebbe non puntare a un'entità del tipo di riferimento e potrebbe essere una rappresentazione trap.

La citazione sopra dice che possiamo trasformare un numero intero in un puntatore. Se lo facciamo (vale a dire, se inseriamo un valore intero in un puntatore anziché un riferimento specifico a un oggetto o una funzione), allora il puntatore "potrebbe non puntare a un'entità di tipo di riferimento" (cioè potrebbe non fornire un riferimento a un oggetto o una funzione). Potrebbe fornirci qualcos'altro. E questo è un posto dove potresti inserire una sorta di handle o ID in un puntatore (cioè il puntatore non punta a un oggetto; sta memorizzando un valore che rappresenta qualcosa, ma quel valore potrebbe non essere un indirizzo).

Quindi sì, come dice Alexey Frunze, è possibile che un puntatore non stia memorizzando un indirizzo su un oggetto o una funzione. È possibile che un puntatore stia invece memorizzando una sorta di "handle" o ID, e puoi farlo assegnando un valore intero arbitrario a un puntatore. Ciò che rappresenta questo handle o ID dipende dal sistema / ambiente / contesto. Finché il tuo sistema / implementazione può dare un senso al valore, sei in buona forma (ma ciò dipende dal valore specifico e dal sistema specifico / implementazione).

Normalmente , un puntatore memorizza un indirizzo su un oggetto o una funzione. Se non memorizza un indirizzo effettivo (in un oggetto o una funzione), il risultato è definito dall'implementazione (il che significa che esattamente cosa succede e che cosa rappresenta il puntatore ora dipende dal sistema e dall'implementazione, quindi potrebbe essere un handle o un ID su un sistema particolare, ma l'utilizzo dello stesso codice / valore su un altro sistema potrebbe causare l'arresto anomalo del programma).

Che alla fine è stato più lungo di quanto pensassi sarebbe ...


3
In un interprete C, un puntatore può contenere un ID / handle / etc senza indirizzo.
Alexey Frunze,

4
@exebook Lo standard non è comunque limitato alla compilazione C.
Alexey Frunze

7
@Lundin Bravo! Ignoriamo di più lo standard! Come se non lo avessimo già ignorato abbastanza e non avessimo prodotto software difettoso e poco portatile a causa di ciò. Inoltre, per favore, non che la domanda originale sia generica e come tale necessiti di una risposta generica.
Alexey Frunze,

3
Quando altri dicono che un puntatore potrebbe essere un handle o qualcos'altro diverso da un indirizzo, non significano solo che è possibile forzare i dati in un puntatore eseguendo il casting di un numero intero in un puntatore. Significano che il compilatore potrebbe utilizzare qualcosa di diverso dagli indirizzi di memoria per implementare i puntatori. Sul processore Alpha con ABI di DEC, un puntatore a funzione non era l'indirizzo della funzione ma era l'indirizzo di un descrittore di una funzione e il descrittore conteneva l'indirizzo della funzione e alcuni dati relativi ai parametri della funzione. Il punto è che lo standard C è molto flessibile.
Eric Postpischil,

5
@Lundin: l'affermazione che i puntatori sono implementati come indirizzi interi sul 100% dei sistemi informatici esistenti nel mondo reale è falsa. Esistono computer con indirizzamento di parole e indirizzamento offset di segmento. I compilatori esistono ancora con supporto per puntatori vicini e lontani. Esistono computer PDP-11, con RSX-11 e Task Builder e i suoi overlay, in cui un puntatore deve identificare le informazioni necessarie per caricare una funzione dal disco. Un puntatore non può avere l'indirizzo di memoria di un oggetto se l'oggetto non è in memoria!
Eric Postpischil,

39

Puntatore vs variabile

In questa immagine,

pointer_p è un puntatore che si trova su 0x12345 e punta a una variabile_v su 0x34567.


16
Non solo questo non affronta la nozione di indirizzo invece del puntatore, ma manca integralmente il punto in cui un indirizzo non è solo un numero intero.
Gilles 'SO- smetti di essere malvagio'

19
-1, questo spiega solo cos'è un puntatore. Non era questa la domanda ... e stai mettendo da parte tutte le complessità di cui si tratta.
alexis

34

Pensare a un puntatore come a un indirizzo è un'approssimazione . Come tutte le approssimazioni, è abbastanza buono per essere utile a volte, ma non è anche esatto, il che significa che fare affidamento su di esso causa problemi.

Un puntatore è come un indirizzo in quanto indica dove trovare un oggetto. Una limitazione immediata di questa analogia è che non tutti i puntatori contengono effettivamente un indirizzo. NULLè un puntatore che non è un indirizzo. Il contenuto di una variabile puntatore può infatti essere di tre tipi:

  • l' indirizzo di un oggetto, che può essere dereferenziato (se pcontiene l'indirizzo di xallora l'espressione *pha lo stesso valore di x);
  • un puntatore nullo , di cui NULLè un esempio;
  • contenuto non valido , che non punta a un oggetto (se pnon contiene un valore valido, *ppotrebbe fare qualsiasi cosa ("comportamento indefinito"), con l'arresto anomalo del programma una possibilità abbastanza comune).

Inoltre, sarebbe più preciso affermare che un puntatore (se valido e non nullo) contiene un indirizzo: un puntatore indica dove trovare un oggetto, ma ci sono più informazioni legate ad esso.

In particolare, un puntatore ha un tipo. Sulla maggior parte delle piattaforme, il tipo di puntatore non ha alcuna influenza in fase di esecuzione, ma ha un'influenza che va oltre il tipo in fase di compilazione. Se pè un puntatore a int( int *p;), quindi p + 1punta a un numero intero che è sizeof(int)byte dopo p(supponendo che p + 1sia ancora un puntatore valido). Se qè un puntatore a charquello punta allo stesso indirizzo di p( char *q = p;), allora q + 1non è lo stesso indirizzo di p + 1. Se si considera il puntatore come un indirizzo, non è molto intuitivo che il "prossimo indirizzo" sia diverso per puntatori diversi nella stessa posizione.

In alcuni ambienti è possibile avere più valori di puntatore con rappresentazioni diverse (diversi schemi di bit in memoria) che puntano alla stessa posizione in memoria. Puoi pensarli come puntatori diversi che contengono lo stesso indirizzo o come indirizzi diversi per la stessa posizione: in questo caso la metafora non è chiara. L' ==operatore ti dice sempre se i due operandi puntano nella stessa posizione, quindi su questi ambienti puoi avere p == qanche se pe qavere modelli di bit diversi.

Ci sono persino ambienti in cui i puntatori trasportano altre informazioni oltre l'indirizzo, come informazioni sul tipo o sull'autorizzazione. Puoi facilmente passare la vita come programmatore senza incontrarli.

Ci sono ambienti in cui diversi tipi di puntatori hanno rappresentazioni diverse. Puoi pensarlo come diversi tipi di indirizzi con rappresentazioni diverse. Ad esempio, alcune architetture hanno puntatori di byte e puntatori di parole o puntatori di oggetti e puntatori di funzioni.

Tutto sommato, pensare ai puntatori come indirizzi non è poi così male se lo si tiene a mente

  • sono solo puntatori validi, non nulli che sono indirizzi;
  • puoi avere più indirizzi per la stessa posizione;
  • non puoi fare l'aritmetica sugli indirizzi e non c'è ordine su di essi;
  • il puntatore contiene anche informazioni sul tipo.

Fare il contrario è molto più problematico. Non tutto ciò che sembra un indirizzo può essere un puntatore . Da qualche parte in profondità qualsiasi puntatore è rappresentato come un modello di bit che può essere letto come un numero intero e si può dire che questo numero intero è un indirizzo. Ma andando dall'altra parte, non tutti i numeri interi sono un puntatore.

Vi sono innanzitutto alcune limitazioni ben note; ad esempio, un numero intero che designa una posizione esterna allo spazio degli indirizzi del programma non può essere un puntatore valido. Un indirizzo disallineato non costituisce un puntatore valido per un tipo di dati che richiede l'allineamento; ad esempio, su una piattaforma in cui è intnecessario un allineamento a 4 byte, 0x7654321 non può essere un int*valore valido .

Tuttavia, va ben oltre, perché quando si trasforma un puntatore in un numero intero, ci si trova in un mondo di problemi. Una grande parte di questo problema è che l'ottimizzazione dei compilatori è molto meglio nella microottimizzazione di quanto la maggior parte dei programmatori si aspetti, quindi il loro modello mentale di come funziona un programma è profondamente sbagliato. Solo perché hai puntatori con lo stesso indirizzo non significa che siano equivalenti. Ad esempio, considera il seguente frammento:

unsigned int x = 0;
unsigned short *p = (unsigned short*)&x;
p[0] = 1;
printf("%u = %u\n", x, *p);

Ci si potrebbe aspettare che su una macchina run-of-the-mill dove sizeof(int)==4e sizeof(short)==2, questo o stampa 1 = 1?(little-endian) o 65536 = 1?(big-endian). Ma sul mio PC Linux a 64 bit con GCC 4.4:

$ c99 -O2 -Wall a.c && ./a.out 
a.c: In function main’:
a.c:6: warning: dereferencing pointer p does break strict-aliasing rules
a.c:5: note: initialized from here
0 = 1?

GCC è abbastanza gentile da avvisarci che cosa non va in questo semplice esempio: in esempi più complessi, il compilatore potrebbe non notare. Dato che pha un tipo diverso da &x, la modifica di ciò che ppunta a non può influire su ciò che &xpunta (al di fuori di alcune eccezioni ben definite). Pertanto il compilatore è libero di mantenere il valore di xin un registro e di non aggiornare questo registro come *pmodifiche. Il programma dereferenzia due puntatori allo stesso indirizzo e ottiene due valori diversi!

La morale di questo esempio è che pensare a un puntatore (non nullo valido) come indirizzo va bene, purché rimanga entro le precise regole del linguaggio C. Il rovescio della medaglia è che le regole del linguaggio C sono complesse e per le quali è difficile ottenere una sensazione intuitiva a meno che non si sappia cosa succede sotto il cofano. E quello che succede sotto il cofano è che il legame tra puntatori e indirizzi è in qualche modo allentato, sia per supportare architetture di processori "esotiche" sia per ottimizzare i compilatori.

Quindi pensa ai puntatori come indirizzi come primo passo nella tua comprensione, ma non seguire troppo questa intuizione.


5
+1. Altre risposte sembrano mancare che un puntatore viene fornito con informazioni sul tipo. Questo è molto più importante dell'indirizzo / ID / qualunque discussione.
undur_gongor

+1 Punti eccellenti sulle informazioni sul tipo. Non sono sicuro che gli esempi di compilatore siano corretti però ... Sembra molto improbabile, ad esempio, che *p = 3abbia successo quando p non è stato inizializzato.
LarsH

@LarsH Hai ragione, grazie, come l'ho scritto? L'ho sostituito con un esempio che dimostra persino il comportamento sorprendente sul mio PC.
Gilles 'SO- smetti di essere malvagio'

1
um, NULL è ((void *) 0) ..?
Aniket Inge

1
@ gnasher729 Il puntatore null è un puntatore. NULLnon lo è, ma per il livello di dettaglio richiesto qui, questa è una distrazione irrilevante. Anche per la programmazione quotidiana, il fatto che NULLpossa essere implementato come qualcosa che non dice "puntatore" non NULLemerge spesso (passando principalmente a una funzione variadica - ma anche lì, se non lo stai lanciando , stai già assumendo che tutti i tipi di puntatore abbiano la stessa rappresentazione).
Gilles 'SO- smetti di essere malvagio' il

19

Un puntatore è una variabile che contiene l'indirizzo di memoria, non l'indirizzo stesso. Tuttavia, è possibile riconoscere un puntatore e accedere alla posizione della memoria.

Per esempio:

int q = 10; /*say q is at address 0x10203040*/
int *p = &q; /*means let p contain the address of q, which is 0x10203040*/
*p = 20; /*set whatever is at the address pointed by "p" as 20*/

Questo è tutto. È così semplice.

inserisci qui la descrizione dell'immagine

Un programma per dimostrare ciò che sto dicendo e il suo output è qui:

http://ideone.com/rcSUsb

Il programma:

#include <stdio.h>

int main(int argc, char *argv[])
{
  /* POINTER AS AN ADDRESS */
  int q = 10;
  int *p = &q;

  printf("address of q is %p\n", (void *)&q);
  printf("p contains %p\n", (void *)p);

  p = NULL;
  printf("NULL p now contains %p\n", (void *)p);
  return 0;
}

5
Può confondere ancora di più. Alice, vedi un gatto? No, riesco a vedere solo il sorriso di un gatto. Quindi dire che il puntatore è un indirizzo o un puntatore è una variabile che contiene un indirizzo o dire che il puntatore è un nome di un concetto che si riferisce all'idea di un indirizzo, fino a che punto gli scrittori di libri possono spingersi in confusi neeeewbies?
exebook

@exebook a quelli conditi in puntatori, è abbastanza semplice. Forse una foto ti aiuterà?
Aniket Inge

5
Un puntatore non contiene necessariamente un indirizzo. In un interprete C, potrebbe essere qualcos'altro, una sorta di ID / handle.
Alexey Frunze,

L '"etichetta" o il nome della variabile è un compilatore / assemblatore e non esiste a livello di macchina, quindi non penso che dovrebbe apparire in memoria.
Ben

1
@Aniket Una variabile puntatore può contenere un valore puntatore. Devi solo archiviare il risultato fopenin una variabile se devi usarlo più di una volta (che, per fopen, è praticamente tutto il tempo).
Gilles 'SO-smetti di essere malvagio' il

16

È difficile dire esattamente cosa significano esattamente gli autori di quei libri. Il fatto che un puntatore contenga o meno un indirizzo dipende da come si definisce un indirizzo e da come si definisce un puntatore.

A giudicare da tutte le risposte scritte, alcune persone assumono che (1) un indirizzo deve essere un numero intero e (2) un puntatore non deve essere virtuale per non essere stato detto nella specifica. Con questi presupposti, quindi chiaramente i puntatori non contengono necessariamente indirizzi.

Tuttavia, vediamo che mentre (2) è probabilmente vero, (1) probabilmente non deve essere vero. E cosa pensare del fatto che il & è chiamato l' indirizzo dell'operatore secondo la risposta di @ CornStalks? Questo significa che gli autori delle specifiche intendono che un puntatore contenga un indirizzo?

Quindi possiamo dire che il puntatore contiene un indirizzo, ma un indirizzo non deve essere un numero intero? Può essere.

Penso che tutto ciò sia un discorso semantico pedante traballante. È totalmente inutile praticamente parlando. Riesci a pensare a un compilatore che genera codice in modo tale che il valore di un puntatore non sia un indirizzo? E allora? È quello che pensavo...

Penso che ciò a cui l'autore del libro (il primo estratto che afferma che i puntatori non sono necessariamente solo indirizzi) si riferisca probabilmente al fatto che un puntatore abbia le informazioni sul tipo intrinseco.

Per esempio,

 int x;
 int* y = &x;
 char* z = &x;

sia y che z sono puntatori, ma y + 1 e z + 1 sono diversi. se sono indirizzi di memoria, quelle espressioni non ti darebbero lo stesso valore?

E qui sta il pensare ai puntatori come se fossero indirizzi di solito porta al dolore . I bug sono stati scritti perché le persone pensano ai puntatori come se fossero indirizzi , e questo di solito porta al dolore .

55555 probabilmente non è un puntatore, sebbene possa essere un indirizzo, ma (int *) 55555 è un puntatore. 55555 + 1 = 55556, ma (int *) 55555 + 1 è 55559 (+/- differenza in termini di dimensioni di (int)).


1
+1 per indicare l'aritmetica del puntatore non è la stessa dell'aritmetica sugli indirizzi.
Kutschkem,

Nel caso dell'8086 a 16 bit, un indirizzo di memoria è descritto da un segmento base + offset, entrambi a 16 bit. Esistono molte combinazioni di segmento base + offset che danno lo stesso indirizzo in memoria. Questo farpuntatore non è solo "un numero intero".
vonbrand

@Vonbrand non capisco perché hai pubblicato quel commento. tale questione è stata discussa come commenti sotto altre risposte. quasi ogni altra risposta presuppone che address = integer e tutto ciò che non è intero non sia address. lo sottolineo semplicemente e noto che può essere o non essere corretto. tutto il mio punto nella risposta è che non è rilevante. è solo pedante e il problema principale non viene affrontato nelle altre risposte.
Grazie

@tang, l'idea "pointer == address" è sbagliata . Che tutti e la loro zia preferita continuino a dire che non è giusto.
vonbrand

@vonbrand, e perché hai fatto quel commento sotto il mio post? Non ho detto che è giusto o sbagliato. In effetti, è giusto in determinati scenari / ipotesi, ma non sempre. Consentitemi di riassumere nuovamente il punto del post (per la seconda volta). tutto il mio punto nella risposta è che non è rilevante. è solo pedante e il problema principale non viene affrontato nelle altre risposte. sarebbe più appropriato commentare le risposte che sostengono che pointer == address o address == integer. vedi i miei commenti sotto il post di Alexey rispetto al segmento: offset.
Grazie

15

Bene, un puntatore è un'astrazione che rappresenta una posizione di memoria. Nota che la citazione non dice che pensare ai puntatori come se fossero indirizzi di memoria è sbagliato, dice solo che "di solito porta al dolore". In altre parole, ti porta ad avere aspettative errate.

La fonte più probabile di dolore è sicuramente l' aritmetica puntatore, che in realtà è uno dei punti di forza di C. Se un puntatore fosse un indirizzo, ti aspetteresti che l'aritmetica del puntatore sia l'aritmetica dell'indirizzo; ma non lo è. Ad esempio, l'aggiunta di 10 a un indirizzo dovrebbe fornire un indirizzo più grande di 10 unità di indirizzamento; ma l'aggiunta di 10 a un puntatore lo incrementa di 10 volte la dimensione del tipo di oggetto a cui punta (e nemmeno la dimensione effettiva, ma arrotondata per eccesso a un limite di allineamento). Con un'architettura int *su ordinaria con numeri interi a 32 bit, l'aggiunta di 10 ad essa aumenterebbe di 40 unità di indirizzamento (byte). I programmatori C esperti ne sono consapevoli e convivono, ma il tuo autore non è evidentemente un fan delle metafore sciatte.

C'è l'ulteriore domanda su come i contenuti del puntatore rappresentino la posizione della memoria: come hanno spiegato molte delle risposte, un indirizzo non è sempre un int (o lungo). In alcune architetture un indirizzo è un "segmento" più un offset. Un puntatore potrebbe anche contenere solo l'offset nel segmento corrente (puntatore "vicino"), che di per sé non è un indirizzo di memoria univoco. E il contenuto del puntatore potrebbe avere solo una relazione indiretta con un indirizzo di memoria quando l'hardware lo capisce. Ma l'autore della citazione citata non menziona nemmeno la rappresentazione, quindi penso che fosse in mente l'equivalenza concettuale, piuttosto che la rappresentazione.


12

Ecco come l'ho spiegato ad alcune persone confuse in passato: un puntatore ha due attributi che influenzano il suo comportamento. Ha un valore , che è (in ambienti tipici) un indirizzo di memoria e un tipo , che indica il tipo e la dimensione dell'oggetto a cui punta.

Ad esempio, dato:

union {
    int i;
    char c;
} u;

Puoi avere tre diversi puntatori che puntano tutti allo stesso oggetto:

void *v = &u;
int *i = &u.i;
char *c = &u.c;

Se si confrontano i valori di questi puntatori, sono tutti uguali:

v==i && i==c

Tuttavia, se aumenti ogni puntatore, vedrai che il tipo a cui puntano diventa rilevante.

i++;
c++;
// You can't perform arithmetic on a void pointer, so no v++
i != c

Le variabili ie cavranno valori diversi a questo punto, poiché i++causa il icontenimento dell'indirizzo dell'intero successivo accessibile e c++fa sì cche punti al carattere indirizzabile successivo. In genere, i numeri interi occupano più memoria dei caratteri, quindi ifiniranno con un valore maggiore rispetto a cquando sono entrambi incrementati.


2
+1 Grazie. Con i puntatori, valore e tipo sono inseparabili come si può separare il corpo dell'uomo dalla sua anima.
Aki Suihkonen,

i == cè mal formato (puoi confrontare i puntatori con tipi diversi solo se c'è una conversione implicita dall'uno all'altro). Inoltre, correggere questo con un cast significa che hai applicato una conversione e quindi è discutibile se la conversione cambia o meno il valore. (Potresti affermare che non lo è, ma poi sta solo affermando la stessa cosa che stavi cercando di dimostrare con questo esempio).
MM

8

Mark Bessey l'ha già detto, ma questo deve essere enfatizzato fino a quando non viene compreso.

Il puntatore ha tanto a che fare con una variabile che con un letterale 3.

Il puntatore è una tupla di un valore (di un indirizzo) e un tipo (con proprietà aggiuntive, come la sola lettura). Il tipo (e eventuali parametri aggiuntivi) possono ulteriormente definire o limitare il contesto; per esempio. __far ptr, __near ptr: qual è il contesto dell'indirizzo: stack, heap, indirizzo lineare, offset da qualche parte, memoria fisica o cosa.

È la proprietà del tipo che rende l'aritmetica del puntatore un po 'diversa dall'aritmetica intera.

Gli esempi contatore di un puntatore di non essere una variabile sono troppi da ignorare

  • fopen restituendo un puntatore FILE. (dov'è la variabile)

  • stack pointer o frame pointer essendo in genere registri non indirizzabili

    *(int *)0x1231330 = 13; - lanciare un valore intero arbitrario in un tipo pointer_of_integer e scrivere / leggere un numero intero senza mai introdurre una variabile

Nel corso della vita di un programma C ci saranno molte altre istanze di puntatori temporanei che non hanno indirizzi - e quindi non sono variabili, ma espressioni / valori con un tipo associato al tempo di compilazione.


8

Sei giusto e sano di mente. Normalmente, un puntatore è solo un indirizzo, quindi puoi lanciarlo su un numero intero ed eseguire qualsiasi aritmetica.

Ma a volte i puntatori sono solo una parte di un indirizzo. Su alcune architetture un puntatore viene convertito in un indirizzo con aggiunta di base o viene utilizzato un altro registro CPU .

Ma in questi giorni, su architettura PC e ARM con un modello di memoria piatta e un linguaggio C compilato in modo nativo, è OK pensare che un puntatore sia un indirizzo intero in un posto nella RAM indirizzabile unidimensionale.


PC ... modello di memoria piatta? cosa sono i selettori?
Grazie

Riight. E quando arriverà il prossimo cambio di architettura, magari con codice separato e spazi dati, o qualcuno tornerà alla venerabile architettura del segmento (che ha un sacco di senso per la sicurezza, potrebbe persino aggiungere qualche chiave al numero del segmento + offset per verificare le autorizzazioni) il tuo gli adorabili "puntatori sono solo numeri interi" si arrestano in modo anomalo.
vonbrand

7

Un puntatore, come qualsiasi altra variabile in C, è fondamentalmente una raccolta di bit che possono essere rappresentati da uno o più unsigned charvalori concatenati (come con qualsiasi altro tipo di cariable, sizeof(some_variable)indicherà il numero di unsigned charvalori). Ciò che rende un puntatore diverso dalle altre variabili è che un compilatore C interpreterà i bit in un puntatore come identificando, in qualche modo, un luogo in cui una variabile può essere memorizzata. In C, a differenza di alcune altre lingue, è possibile richiedere spazio per più variabili e quindi convertire un puntatore a qualsiasi valore in quel set in un puntatore a qualsiasi altra variabile all'interno di quel set.

Molti compilatori implementano i puntatori utilizzando i loro bit per archiviare gli indirizzi macchina reali, ma questa non è l'unica implementazione possibile. Un'implementazione potrebbe mantenere un array - non accessibile al codice utente - che elenca l'indirizzo hardware e la dimensione allocata di tutti gli oggetti di memoria (insiemi di variabili) che un programma stava usando e che ciascun puntatore contenga un indice in un array lungo con un offset da quell'indice. Tale progettazione consentirebbe a un sistema non solo di limitare il codice al solo funzionamento sulla memoria di sua proprietà, ma garantirebbe anche che un puntatore a un elemento di memoria non possa essere accidentalmente convertito in un puntatore a un altro elemento di memoria (in un sistema che utilizza hardware indirizzi, se fooe barsono matrici di 10 elementi archiviati consecutivamente in memoria, un puntatore all'elemento "undicesimo" difoopotrebbe invece puntare al primo elemento di bar, ma in un sistema in cui ogni "puntatore" è un ID oggetto e un offset, il sistema potrebbe intercettare se il codice provasse a indicizzare un puntatore foooltre l'intervallo assegnato. Sarebbe anche possibile per un tale sistema eliminare i problemi di frammentazione della memoria, poiché gli indirizzi fisici associati a qualsiasi puntatore potrebbero essere spostati.

Si noti che mentre i puntatori sono in qualche modo astratti, non sono abbastanza astratti per consentire a un compilatore C pienamente conforme agli standard di implementare un garbage collector. Il compilatore C specifica che ogni variabile, inclusi i puntatori, è rappresentata come una sequenza di unsigned charvalori. Data qualsiasi variabile, si può scomporla in una sequenza di numeri e successivamente riconvertire quella sequenza di numeri in una variabile del tipo originale. Di conseguenza, sarebbe possibile per un programmacallocun po 'di spazio di archiviazione (ricevendo un puntatore ad esso), archiviare qualcosa lì, scomporre il puntatore in una serie di byte, visualizzare quelli sullo schermo e quindi cancellare tutti i riferimenti ad essi. Se il programma accettasse quindi alcuni numeri dalla tastiera, li ricostituisse in un puntatore e quindi provasse a leggere i dati da quel puntatore e se l'utente immettesse gli stessi numeri che il programma aveva precedentemente visualizzato, il programma sarebbe tenuto a produrre i dati che era stato memorizzato nella callocmemoria. Poiché non esiste un modo concepibile in cui il computer possa sapere se l'utente abbia creato una copia dei numeri visualizzati, non sarebbe possibile che il computer possa sapere se in futuro sarà possibile accedere alla memoria di cui sopra.


A grande sovraccarico, forse potresti rilevare qualsiasi uso del valore del puntatore che potrebbe "trapelare" il suo valore numerico e bloccare l'allocazione in modo che il garbage collector non la raccolga o trasferisca (a meno che non freesia chiamato esplicitamente, ovviamente). Se l'implementazione risultante sarebbe tanto utile è un'altra cosa, poiché la sua capacità di raccolta potrebbe essere troppo limitata, ma potresti almeno chiamarla garbage collector :-) L'assegnazione del puntatore e l'aritmetica non "perderebbero" il valore, ma qualsiasi accesso a una char*di origine sconosciuta dovrebbe essere verificato.
Steve Jessop,

@SteveJessop: Penso che un tale design sarebbe peggio che inutile, dal momento che sarebbe impossibile per il codice sapere quali puntatori dovevano essere liberati. I garbage collector che presumono che tutto ciò che assomiglia a un puntatore sia uno può essere eccessivamente conservativo, ma in genere le cose che assomigliano - ma non lo sono - ai puntatori hanno la possibilità di cambiare, evitando così perdite di memoria "permanenti". Avere qualsiasi azione che sembri decomporre un puntatore in byte per congelare permanentemente il puntatore è una ricetta garantita per perdite di memoria.
supercat

Penso che fallirebbe comunque per motivi di prestazioni: se vuoi che il tuo codice venga eseguito così lentamente perché ogni accesso è controllato, non scriverlo in C ;-) Ho maggiori speranze per l'ingegnosità dei programmatori C di te, poiché penso che sia scomodo, probabilmente non è plausibile evitare di bloccare inutilmente le allocazioni. Comunque, C ++ definisce "puntatori derivati ​​in modo sicuro" proprio per affrontare questo problema, quindi sappiamo cosa fare se vogliamo mai aumentare l'astrattezza dei puntatori C al livello in cui supportano una garbage collection ragionevolmente efficace.
Steve Jessop,

@SteveJessop: Affinché un sistema GC sia utile, dovrebbe essere in grado di rilasciare in modo affidabile la memoria su cui freenon è stato chiamato, o impedire a qualsiasi riferimento a un oggetto liberato di diventare un riferimento a un oggetto attivo [anche quando si utilizzano risorse che richiedono gestione esplicita della durata, GC può ancora svolgere utilmente quest'ultima funzione]; un sistema GC che a volte considera falsamente gli oggetti come aventi riferimenti dinamici ad essi può essere utilizzabile se la probabilità che N oggetti vengano bloccati inutilmente contemporaneamente si avvicina a zero quando N diventa grande . A meno che uno non sia disposto a
segnalare

... per il codice che è C ++ valido, ma per il quale il compilatore non sarebbe in grado di dimostrare che un puntatore non può mai essere convertito in una forma irriconoscibile, non vedo come si possa evitare il rischio che un programma che in realtà mai utilizza i puntatori poiché gli interi potrebbero essere erroneamente considerati come tali.
supercat

6

Un puntatore è un tipo di variabile che è nativamente disponibile in C / C ++ e contiene un indirizzo di memoria. Come qualsiasi altra variabile ha un indirizzo proprio e occupa memoria (la quantità è specifica della piattaforma).

Un problema che vedrai a causa della confusione sta provando a cambiare il referente all'interno di una funzione semplicemente passando il puntatore per valore. Ciò comporterà una copia del puntatore nell'ambito della funzione e qualsiasi modifica apportata al punto in cui questo nuovo puntatore "punta" non cambierà il referente del puntatore nell'ambito che ha invocato la funzione. Per modificare il puntatore effettivo all'interno di una funzione, si passa normalmente un puntatore a un puntatore.


1
Generalmente, è un handle / ID. Di solito, è un indirizzo semplice.
Alexey Frunze,

Ho adattato la mia risposta per essere un po 'più PC alla definizione di Handle in Wikipedia. Mi piace fare riferimento ai puntatori come un'istanza particolare di un handle, poiché un handle può essere semplicemente un riferimento a un puntatore.
Matthew Sanders,

6

BREVE SINTESI (che metterò anche in cima):

(0) Pensare ai puntatori come indirizzi è spesso un buon strumento di apprendimento ed è spesso l'implementazione effettiva di puntatori a tipi di dati ordinari.

(1) Ma su molti, forse la maggior parte, i compilatori puntatori a funzioni non sono indirizzi, ma sono più grandi di un indirizzo (in genere 2x, a volte più), o in realtà sono puntatori a una struttura in memoria che contiene gli indirizzi di funzioni e cose come un pool costante.

(2) I puntatori ai membri dei dati e i puntatori ai metodi sono spesso ancora più strani.

(3) Codice x86 legacy con problemi di puntatore FAR e NEAR

(4) Diversi esempi, in particolare IBM AS / 400, con "puntatori fat" sicuri.

Sono sicuro che puoi trovare di più.

DETTAGLIO:

UMMPPHHH !!!!! Molte delle risposte finora sono risposte "programmatore weenie" abbastanza tipiche - ma non weenie del compilatore o weenie hardware. Dal momento che fingo di essere un weenie hardware e spesso lavoro con weenies di compilatore, lasciami buttare i miei due centesimi:

Su molti compilatori C, probabilmente la maggior parte, un puntatore a dati di tipo Tè, in effetti, l'indirizzo di T.

Belle.

Ma, anche su molti di questi compilatori, alcuni puntatori NON sono indirizzi. Puoi dirlo guardando sizeof(ThePointer).

Ad esempio, i puntatori alle funzioni sono talvolta molto più grandi degli indirizzi ordinari. Oppure, possono comportare un livello di riferimento indiretto. Questo articolofornisce una descrizione che coinvolge il processore Intel Itanium, ma ne ho visti altri. In genere, per chiamare una funzione è necessario conoscere non solo l'indirizzo del codice funzione, ma anche l'indirizzo del pool di costanti della funzione: un'area di memoria da cui vengono caricate le costanti con una singola istruzione di caricamento, piuttosto che il compilatore debba generare una costante a 64 bit tra più istruzioni Load Immediate e Shift e OR. Pertanto, anziché un singolo indirizzo a 64 bit, sono necessari 2 indirizzi a 64 bit. Alcune ABI (Application Binary Interfaces) lo spostano di 128 bit, mentre altri usano un livello di indiretta, con il puntatore a funzione che in realtà è l'indirizzo di un descrittore di funzione che contiene i 2 indirizzi effettivi appena menzionati. Che è migliore? Dipende dal tuo punto di vista: prestazioni, dimensioni del codice, e alcuni problemi di compatibilità - spesso il codice presuppone che un puntatore possa essere lanciato su un long o un long long, ma può anche presumere che il long long sia esattamente 64 bit. Tale codice potrebbe non essere conforme agli standard, ma tuttavia i clienti potrebbero desiderare che funzioni.

Molti di noi hanno dolorosi ricordi della vecchia architettura segmentata Intel x86, con NEAR POINTER e FAR POINTERS. Per fortuna ormai sono quasi estinti, quindi solo un breve riassunto: in modalità reale a 16 bit, l'indirizzo lineare reale era

LinearAddress = SegmentRegister[SegNum].base << 4 + Offset

Considerando che in modalità protetta, potrebbe essere

LinearAddress = SegmentRegister[SegNum].base + offset

con l'indirizzo risultante verificato contro un limite impostato nel segmento. Alcuni programmi utilizzavano dichiarazioni di puntatori FAR e NEAR C / C ++ non standard, ma molti hanno appena detto *T--- ma c'erano opzioni di compilatore e linker quindi, ad esempio, i puntatori di codice potrebbero essere vicini a puntatori, solo un offset di 32 bit rispetto a qualsiasi cosa sia in il registro CS (Code Segment), mentre i puntatori di dati potrebbero essere puntatori FAR, specificando sia un numero di segmento a 16 bit che un offset a 32 bit per un valore di 48 bit. Ora, entrambe queste quantità sono sicuramente correlate all'indirizzo, ma poiché non hanno le stesse dimensioni, quale di esse è l'indirizzo? Inoltre, i segmenti avevano anche permessi - sola lettura, lettura-scrittura, eseguibile - oltre a cose relative all'indirizzo reale.

Un esempio più interessante, IMHO, è (o, forse, era) la famiglia IBM AS / 400. Questo computer è stato uno dei primi a implementare un sistema operativo in C ++. I puntatori su questo machime erano in genere 2 volte la dimensione effettiva dell'indirizzo, ad esempio come questa presentazionedice puntatori a 128 bit, ma gli indirizzi effettivi erano 48-64 bit e, di nuovo, alcune informazioni extra, quella che viene chiamata una capacità, che forniva autorizzazioni come lettura, scrittura e un limite per prevenire l'overflow del buffer. Sì: puoi farlo in modo compatibile con C / C ++ - e se questo fosse onnipresente, il PLA cinese e la mafia slava non andrebbero ad hackerare in così tanti sistemi informatici occidentali. Ma storicamente la maggior parte della programmazione C / C ++ ha trascurato la sicurezza per le prestazioni. Cosa più interessante, la famiglia AS400 ha permesso al sistema operativo di creare puntatori sicuri, che potevano essere dati a codice non privilegiato, ma che il codice non privilegiato non poteva forgiare o manomettere. Ancora una volta, la sicurezza, e sebbene conforme agli standard, il codice C / C ++ molto sciatto e non conforme agli standard non funzionerà in un sistema così sicuro. Ancora una volta, ci sono standard ufficiali,

Ora, uscirò dalla mia soapbox di sicurezza e menzionerò altri modi in cui i puntatori (di vari tipi) spesso non sono realmente indirizzi: puntatori ai membri dei dati, puntatori ai metodi delle funzioni dei membri e le loro versioni statiche sono più grandi di un indirizzo ordinario. Come dice questo post :

Esistono molti modi per risolverlo [problemi legati all'ereditarietà singola o multipla e all'eredità virtuale]. Ecco come il compilatore di Visual Studio decide di gestirlo: un puntatore a una funzione membro di una classe ereditata da moltiplicatori è in realtà una struttura. "E proseguono dicendo" Lanciare un puntatore a funzione può cambiare le sue dimensioni! ".

Come probabilmente puoi immaginare dal mio pontificare sulla (in) sicurezza, sono stato coinvolto in progetti hardware / software C / C ++ in cui un puntatore è stato trattato più come una capacità che come un indirizzo non elaborato.

Potrei continuare, ma spero che tu abbia avuto l'idea.

BREVE SINTESI (che metterò anche in cima):

(0) pensare ai puntatori come indirizzi è spesso un buon strumento di apprendimento ed è spesso l'implementazione effettiva di puntatori a tipi di dati ordinari.

(1) Ma su molti, forse la maggior parte, i compilatori puntatori a funzioni non sono indirizzi, ma sono più grandi di un indirizzo (in genere 2X, a volte più), o in realtà sono puntatori a una struttura in memoria che contiene gli indirizzi di funzioni e cose come un pool costante.

(2) I puntatori ai membri dei dati e i puntatori ai metodi sono spesso ancora più strani.

(3) Codice x86 legacy con problemi di puntatore FAR e NEAR

(4) Diversi esempi, in particolare IBM AS / 400, con "puntatori fat" sicuri.

Sono sicuro che puoi trovare di più.


In modalità reale a 16 bit LinearAddress = SegmentRegister.Selector * 16 + Offset(nota i tempi 16, non spostati di 16). In modalità protetta LinearAddress = SegmentRegister.base + offset(nessuna moltiplicazione di alcun tipo; la base del segmento viene memorizzata nel GDT / LDT e memorizzata nella cache del registro dei segmenti così com'è ).
Alexey Frunze,

Hai anche ragione sulla base del segmento. Mi ero sbagliato. È il limite di segmento facoltativamente multiplo per 4K. La base del segmento deve solo essere decodificata dall'hardware quando carica un descrittore di segmento dalla memoria in un registro di segmenti.
Krazy Glew,

4

Un puntatore è solo un'altra variabile che viene utilizzata per contenere l'indirizzo di una posizione di memoria (di solito l'indirizzo di memoria di un'altra variabile).


Quindi, la punta è in realtà un indirizzo di memoria? Non sei d'accordo con l'autore? Sto solo cercando di capire.
d0rmLife

La funzione principale del puntatore è di puntare a qualcosa. Non è possibile definire esattamente come raggiungere questo obiettivo e se esiste o meno un indirizzo reale. Un puntatore potrebbe essere solo un ID / handle, non un indirizzo reale.
Alexey Frunze,

4

Puoi vederlo in questo modo. Un puntatore è un valore che rappresenta un indirizzo nello spazio di memoria indirizzabile.


2
Un puntatore non deve necessariamente contenere l'indirizzo di memoria reale al suo interno. Vedi la mia risposta e i commenti sotto.
Alexey Frunze,

cosa .... il puntatore alla prima variabile nello stack non stampa 0. stampa la parte superiore (o inferiore) del frame dello stack a seconda di come è implementata.
Grazie

@thang Per la prima variabile la parte superiore e inferiore sono uguali. E qual è l'indirizzo della parte superiore o inferiore in questo caso dello stack?
Valentin Radu

@ValentinRadu, perché non lo provi .. ovviamente non l'hai provato.
Grazie

2
@thang Hai ragione, ho fatto delle ipotesi pessime, a mia difesa sono le 5 del mattino qui.
Valentin Radu

3

Un puntatore è solo un'altra variabile che può contenere l'indirizzo di memoria di solito di un'altra variabile. Un puntatore essendo una variabile ha anche un indirizzo di memoria.


1
Non necessariamente un indirizzo. A proposito, hai letto le risposte e i commenti esistenti prima di pubblicare la tua risposta?
Alexey Frunze,

3

Il puntatore CA è molto simile a un indirizzo di memoria ma con dettagli dipendenti dalla macchina sottratti, nonché alcune funzioni non presenti nel set di istruzioni di livello inferiore.

Ad esempio, un puntatore C è tipizzato in modo relativamente ricco. Se si incrementa un puntatore attraverso una matrice di strutture, si salta piacevolmente da una struttura all'altra.

I puntatori sono soggetti alle regole di conversione e forniscono il controllo del tipo di tempo di compilazione.

Esiste uno speciale valore "puntatore nullo" che è portatile a livello di codice sorgente, ma la cui rappresentazione può differire. Se si assegna una costante intera il cui valore è zero a un puntatore, quel puntatore assume il valore del puntatore null. Idem se inizializzi un puntatore in quel modo.

Un puntatore può essere usato come una variabile booleana: verifica true se è diverso da null e false se è null.

In un linguaggio macchina, se il puntatore null è un indirizzo divertente come 0xFFFFFFFF, potrebbe essere necessario avere test espliciti per quel valore. C lo nasconde da te. Anche se il puntatore null è 0xFFFFFFFF, è possibile testarlo utilizzando if (ptr != 0) { /* not null! */}.

L'uso di puntatori che sovvertono il sistema dei tipi porta a comportamenti indefiniti, mentre un codice simile nel linguaggio macchina potrebbe essere ben definito. Gli assemblatori assembleranno le istruzioni che hai scritto, ma i compilatori C ottimizzeranno in base al presupposto che non hai fatto nulla di male. Se un float *ppuntatore punta a una long nvariabile e *p = 0.0viene eseguito, il compilatore non è tenuto a gestirlo. Un uso successivo di nnon dovrà necessariamente leggere il modello di bit del valore float, ma forse sarà un accesso ottimizzato che si basa sul presupposto "aliasing rigoroso" che nnon è stato toccato! Cioè, il presupposto che il programma sia ben educato, e quindi pnon dovrebbe essere puntato verso n.

In C, i puntatori al codice e i puntatori ai dati sono diversi, ma su molte architetture, gli indirizzi sono gli stessi. I compilatori C possono essere sviluppati con puntatori "grassi", anche se l'architettura di destinazione no. Puntatori fat significa che i puntatori non sono solo indirizzi macchina, ma contengono altre informazioni, come informazioni sulla dimensione dell'oggetto puntato, per il controllo dei limiti. I programmi scritti in modo portabile verranno facilmente portati su tali compilatori.

Come puoi vedere, ci sono molte differenze semantiche tra indirizzi macchina e puntatori C.


I puntatori NULL non funzionano come pensi che facciano su tutte le piattaforme - vedi la mia risposta a CiscoIPPhone sopra. NULL == 0 è un presupposto valido solo su piattaforme x86. La Convenzione afferma che le nuove piattaforme dovrebbero corrispondere a x86, anche se in particolare nel mondo embedded non è così. Modifica: Inoltre, C non fa nulla per sottrarre il valore di un puntatore dall'hardware - "ptr! = 0" non funzionerà come test NULL su una piattaforma in cui NULL! = 0.
DX-MON

1
DX-MON, questo è completamente sbagliato per lo standard C. NULL è diviso in 0 e possono essere usati in modo intercambiabile nelle istruzioni. Il fatto che la rappresentazione del puntatore NULL nell'hardware non sia tutti 0 bit non è rilevante per come è rappresentata nel codice sorgente.
Mark Bessey,

@ DX-MON Temo che non stai lavorando con i fatti corretti. In C, un'espressione di costante integrale funge da costante di puntatore null, indipendentemente dal fatto che il puntatore null sia l'indirizzo null. Se si conosce un compilatore C in cui ptr != 0non è un test nullo, si prega di rivelare la sua identità (ma prima di farlo, inviare una segnalazione di bug al fornitore).
Kaz

Vedo a cosa stai arrivando, ma i tuoi commenti sui puntatori null sono incoerenti perché stai confondendo puntatori e indirizzi di memoria - esattamente ciò che la citazione citata nella domanda consiglia di evitare! L'istruzione corretta: C definisce il puntatore null come zero, indipendentemente dal fatto che un indirizzo di memoria a zero offset sia legale o meno.
alexis

1
@alexis Capitolo e verso, per favore. C non definisce il puntatore null come zero. C definisce zero (o qualsiasi espressione di costante integrale il cui valore è zero) come sintassi per indicare una costante puntatore null. faqs.org/faqs/C-faq/faq (sezione 5).
Kaz

3

Prima di capire i puntatori dobbiamo capire gli oggetti. Gli oggetti sono entità esistenti e hanno un identificatore di posizione chiamato indirizzo. Un puntatore è semplicemente una variabile come qualsiasi altra variabile Ccon un tipo chiamato il pointercui contenuto viene interpretato come l'indirizzo di un oggetto che supporta la seguente operazione.

+ : A variable of type integer (usually called offset) can be added to yield a new pointer
- : A variable of type integer (usually called offset) can be subtracted to yield a new pointer
  : A variable of type pointer can be subtracted to yield an integer (usually called offset)
* : De-referencing. Retrieve the value of the variable (called address) and map to the object the address refers to.
++: It's just `+= 1`
--: It's just `-= 1`

Un puntatore è classificato in base al tipo di oggetto a cui si riferisce attualmente. L'unica parte delle informazioni che contano è la dimensione dell'oggetto.

Qualsiasi oggetto supporta un'operazione, &(indirizzo di), che recupera l'identificatore di posizione (indirizzo) dell'oggetto come tipo di oggetto puntatore. Ciò dovrebbe attenuare la confusione che circonda la nomenclatura poiché avrebbe senso chiamare &come un'operazione di un oggetto anziché un puntatore il cui tipo risultante è un puntatore del tipo di oggetto.

Nota Durante questa spiegazione, ho lasciato fuori il concetto di memoria.


Mi piace la tua spiegazione sulla realtà astratta di un puntatore generale in un sistema generale. Ma forse discutere di memoria sarebbe utile. In effetti, parlando da solo, so che sarebbe ...! Penso che discutere della connessione possa essere molto utile per comprendere il quadro generale. +1 comunque :)
d0rmLife

@ d0rmLife: hai altre spiegazioni nelle altre risposte che coprono il quadro generale. Volevo solo dare una spiegazione matematica astratta come un'altra visione. Anche IMHO, creerebbe meno confusione nella chiamata &come "Indirizzo di" poiché è più legato a un Oggetto piuttosto che al puntatore in sé "
Abhijit

Senza offesa, ma deciderò da solo quale spiegazione sufficiente sia. Un libro di testo non è sufficiente per spiegare completamente le strutture dei dati e l'allocazione della memoria. ;) .... comunque, la tua risposta è comunque utile , anche se non è una novità.
d0rmLife

Non ha senso gestire i puntatori senza il concetto di memoria . Se l'oggetto esiste senza memoria, deve trovarsi in un luogo in cui non esiste un indirizzo, ad esempio nei registri. Essere in grado di usare '&' presuppone memoria.
Aki Suihkonen,

3

Un indirizzo viene utilizzato per identificare un pezzo di memoria a dimensione fissa, di solito per ogni byte, come un numero intero. Questo è precisamente chiamato come indirizzo byte , che è anche usato dall'ISO C. Possono esserci altri metodi per costruire un indirizzo, ad es. Per ogni bit. Tuttavia, viene utilizzato così spesso solo l'indirizzo byte, di solito omettiamo "byte".

Tecnicamente, un indirizzo non è mai un valore in C, perché la definizione del termine "valore" in (ISO) C è:

significato preciso del contenuto di un oggetto quando interpretato come avente un tipo specifico

(Sottolineato da me.) Tuttavia, non esiste un tale "tipo di indirizzo" in C.

Il puntatore non è lo stesso. Il puntatore è un tipo di tipo nel linguaggio C. Esistono diversi tipi di puntatori distinti. Essi non necessariamente obbediscono alla serie identiche di regole della lingua, ad esempio l'effetto di ++un valore di tipo int*vs. char*.

Un valore in C può essere di tipo puntatore. Questo si chiama valore del puntatore . Per essere chiari, un valore di puntatore non è un puntatore nel linguaggio C. Ma siamo abituati a mescolarli insieme, perché in C non è probabile che sia ambiguo: se chiamiamo un'espressione pcome "puntatore", è semplicemente un valore di puntatore ma non un tipo, poiché un tipo denominato in C non è espresso da un'espressione , ma da un nome-tipo o un nome-typedef .

Alcune altre cose sono sottili. Come utente C, in primo luogo, si dovrebbe sapere cosa objectsignifica:

regione di archiviazione dei dati nell'ambiente di esecuzione, il cui contenuto può rappresentare valori

Un oggetto è un'entità per rappresentare valori, che sono di un tipo specifico. Un puntatore è un tipo di oggetto . Quindi se dichiariamo int* p;, allora psignifica "un oggetto di tipo puntatore" o un "oggetto puntatore".

Nota che non esiste una "variabile" definita normalmente dallo standard (in realtà non viene mai usato come nome da ISO C nel testo normativo). Tuttavia, informalmente, chiamiamo un oggetto una variabile, come fa qualche altra lingua. (Ma ancora non esattamente, ad es. In C ++ una variabile può essere normalmente di tipo di riferimento , che non è un oggetto.) Le frasi "oggetto puntatore" o "variabile puntatore" sono talvolta trattate come "valore puntatore" come sopra, con un probabile leggera differenza. (Un altro set di esempi è "array".)

Poiché il puntatore è un tipo e l'indirizzo è effettivamente "non tipizzato" in C, un valore del puntatore approssimativamente "contiene" un indirizzo. E un'espressione di tipo puntatore può produrre un indirizzo, ad es

ISO C11 6.5.2.3

3 L' &operatore unario fornisce l'indirizzo del suo operando.

Nota che questa formulazione è stata introdotta da WG14 / N1256, ovvero ISO C99: TC3. Nel C99 c'è

3 L' &operatore unario restituisce l'indirizzo del suo operando.

Riflette l'opinione del comitato: un indirizzo non è un valore puntatore restituito dall'operatore unario &.

Nonostante la dicitura sopra, ci sono ancora alcuni problemi anche negli standard.

ISO C11 6.6

9 Una costante di indirizzo è un puntatore nullo, un puntatore a un valore nominale che designa un oggetto con durata di memorizzazione statica o un puntatore a un designatore di funzioni

ISO C ++ 11 5.19

3 ... Un'espressione della costante di indirizzo è un'espressione costante del core del valore del tipo di puntatore che restituisce l'indirizzo di un oggetto con durata di memorizzazione statica, l'indirizzo di una funzione o un valore del puntatore nullo o un'espressione della costante del core del valore di tipo std::nullptr_t. ...

(La recente bozza standard C ++ utilizza un'altra formulazione, quindi non c'è questo problema.)

In realtà sia "costante di indirizzo" in C che "espressione di costante di indirizzo" in C ++ sono espressione costante di tipi di puntatore (o almeno tipi di "puntatore" dal C ++ 11).

E l' &operatore unario incorporato viene chiamato come "indirizzo-di" in C e C ++; allo stesso modo, std::addressofè stato introdotto in C ++ 11.

Queste denominazioni possono portare malintesi. L'espressione risultato è di tipo puntatore, così che sarebbero stati interpretati come: il risultato contiene / produce un indirizzo, piuttosto che è un indirizzo.


2

Dice "perché confonde quelli che non sanno di cosa trattano gli indirizzi" - inoltre, è vero: se impari di cosa tratta gli indirizzi, non sarai confuso. Teoricamente, il puntatore è una variabile che punta a un altro, praticamente contiene un indirizzo, che è l'indirizzo della variabile a cui punta. Non so perché nascondere questo fatto, non è una scienza missilistica. Se capisci i puntatori, ti avvicinerai di un passo per capire come funzionano i computer. Vai avanti!


2

Vieni a pensarci, penso che sia una questione di semantica. Non penso che l'autore abbia ragione, dal momento che lo standard C si riferisce a un puntatore che contiene un indirizzo per l'oggetto di riferimento, come altri hanno già menzionato qui. Tuttavia, address! = Memory address. Un indirizzo può essere qualsiasi cosa secondo lo standard C anche se alla fine porterà a un indirizzo di memoria, il puntatore stesso può essere un id, un offset + selettore (x86), qualsiasi cosa fintanto che può descrivere (dopo aver mappato) qualsiasi memoria indirizzo nello spazio indirizzabile.


Un puntatore contiene un indirizzo (o no, se è nullo). Ma è molto diverso dal fatto che sia un indirizzo: ad esempio, due puntatori allo stesso indirizzo ma con un tipo diverso non sono equivalenti in molte situazioni.
Gilles 'SO- smetti di essere malvagio'

@Gilles Se vedi "essere", come in int i=5-> i è 5, allora il puntatore è l'indirizzo yes. Inoltre, null ha anche un indirizzo. Di solito un indirizzo di scrittura non valido (ma non necessariamente, vedi la modalità x86-real), ma comunque un indirizzo. Esistono davvero solo 2 requisiti per null: è garantito un confronto diseguale tra un puntatore e un oggetto reale e due puntatori null comparano uguali.
Valentin Radu

Al contrario, è garantito che un puntatore null non sia uguale all'indirizzo di nessun oggetto. Dereferenziare un puntatore nullo è un comportamento indefinito. Un grosso problema nel dire che "il puntatore è l'indirizzo" è che funzionano in modo diverso. Se pè un puntatore, p+1l'indirizzo non viene sempre incrementato di 1.
Gilles 'SO- smetti di essere malvagio'

Rileggete il commento per favore, it's guaranteed to compare unequal to a pointer to an actual object. Per quanto riguarda l'aritmetica del puntatore, non vedo il punto, il valore del puntatore è ancora un indirizzo, anche se l'operazione "+" non aggiungerà necessariamente un byte ad esso.
Valentin Radu

1

Un altro modo in cui un puntatore C o C ++ differisce da un semplice indirizzo di memoria a causa dei diversi tipi di puntatore che non ho visto nelle altre risposte (anche se, date le loro dimensioni totali, potrei averlo ignorato). Ma è probabilmente il più importante, perché anche i programmatori C / C ++ esperti possono inciamparvi:

Il compilatore può presumere che i puntatori di tipi incompatibili non puntino allo stesso indirizzo anche se lo fanno chiaramente, il che potrebbe dare un comportamento che non sarebbe possibile con un semplice modello di indirizzo pointer ==. Considera il seguente codice (supponendo sizeof(int) = 2*sizeof(short)):

unsigned int i = 0;
unsigned short* p = (unsigned short*)&i;
p[0]=p[1]=1;

if (i == 2 + (unsigned short)(-1))
{
  // you'd expect this to execute, but it need not
}

if (i == 0)
{
  // you'd expect this not to execute, but it actually may do so
}

Si noti che esiste un'eccezione char*, quindi char*è possibile manipolare i valori (anche se non molto portabile).


0

Riepilogo rapido: l'indirizzo CA è un valore, in genere rappresentato come indirizzo di memoria a livello di macchina, con un tipo specifico.

La parola non qualificata "puntatore" è ambigua. C ha oggetti puntatore (variabili), tipi di puntatore , espressioni di puntatore e valori di puntatore .

È molto comune usare la parola "puntatore" per indicare "oggetto puntatore", e ciò può creare confusione, motivo per cui provo a usare "puntatore" come aggettivo anziché come sostantivo.

Lo standard C, almeno in alcuni casi, usa la parola "puntatore" per indicare "valore del puntatore". Ad esempio, la descrizione di malloc dice che "restituisce un puntatore nullo o un puntatore allo spazio allocato".

Quindi qual è un indirizzo in C? È un valore di puntatore, ovvero un valore di un determinato tipo di puntatore. (Ad eccezione del fatto che un valore di puntatore nullo non viene necessariamente definito "indirizzo", poiché non è l'indirizzo di nulla).

La descrizione dello standard &dell'operatore unario dice che "fornisce l'indirizzo del suo operando". Al di fuori dello standard C, la parola "indirizzo" viene comunemente utilizzata per fare riferimento a un indirizzo di memoria (fisico o virtuale), in genere una parola di dimensioni (qualunque sia una "parola" su un determinato sistema).

L'indirizzo "AC" viene in genere implementato come indirizzo macchina, proprio come un intvalore C viene in genere implementato come parola macchina. Ma un indirizzo C (valore del puntatore) è più di un semplice indirizzo macchina. È un valore generalmente rappresentato come un indirizzo macchina ed è un valore con un tipo specifico .


0

Un valore di puntatore è un indirizzo. Una variabile puntatore è un oggetto che può memorizzare un indirizzo. Questo è vero perché è quello che lo standard definisce un puntatore. È importante dirlo ai novizi in C perché i novizi in C spesso non sono chiari sulla differenza tra un puntatore e la cosa a cui punta (vale a dire, non conoscono la differenza tra una busta e un edificio). La nozione di un indirizzo (ogni oggetto ha un indirizzo e questo è ciò che memorizza un puntatore) è importante perché lo ordina.

Tuttavia, lo standard parla a un determinato livello di astrazione. Quelle persone di cui parla l'autore che "sanno di cosa parlano gli indirizzi", ma che sono nuovi in ​​C, devono necessariamente aver imparato a conoscere gli indirizzi con un diverso livello di astrazione, magari programmando il linguaggio assembly. Non vi è alcuna garanzia che l'implementazione C utilizzi la stessa rappresentazione per gli indirizzi utilizzati dagli opcode della CPU (indicati come "l'indirizzo del negozio" in questo passaggio), di cui queste persone già conoscono.

Continua a parlare di "manipolazione dell'indirizzo perfettamente ragionevole". Per quanto riguarda lo standard C, in pratica non esiste una "manipolazione dell'indirizzo perfettamente ragionevole". L'aggiunta è definita sui puntatori e questo è fondamentalmente. Certo, puoi convertire un puntatore in un numero intero, eseguire alcune operazioni bit per bit o aritmetiche, quindi riconvertirlo. Questo non è garantito per funzionare dallo standard, quindi prima di scrivere quel codice è meglio sapere come la tua particolare implementazione C rappresenta i puntatori ed esegue quella conversione. E ' probabilmente utilizza la rappresentazione indirizzo che ci si aspetta, ma non è così che la colpa tua, perché non hai letto il manuale. Non è confusione, è una procedura di programmazione errata ;-)

In breve, C usa un concetto più astratto di indirizzo rispetto all'autore.

Il concetto dell'autore di un indirizzo ovviamente non è la parola di livello più basso sulla questione. Per quanto riguarda le mappe di memoria virtuale e l'indirizzamento della RAM fisica su più chip, il numero a cui si dice che la CPU è "l'indirizzo del negozio" a cui si desidera accedere non ha praticamente nulla a che fare con il luogo in cui i dati desiderati si trovano effettivamente nell'hardware. Sono tutti livelli di indiretta e rappresentazione, ma l'autore ne ha scelto uno da privilegiare. Se hai intenzione di farlo quando parli di C, scegli il livello C da privilegiare !

Personalmente non penso che le osservazioni dell'autore siano altrettanto utili, tranne nel contesto dell'introduzione di C ai programmatori di assembly. Non è certamente utile per coloro che provengono da lingue di livello superiore affermare che i valori del puntatore non sono indirizzi. Sarebbe molto meglio riconoscere la complessità piuttosto che dire che la CPU ha il monopolio nel dire cos'è un indirizzo e quindi che i valori del puntatore C "non" sono indirizzi. Sono indirizzi, ma possono essere scritti in una lingua diversa dagli indirizzi che intende. Distinguere le due cose nel contesto di C come "indirizzo" e "indirizzo del negozio" sarebbe adeguato, credo.


0

Semplicemente per dire che i puntatori sono in realtà parte offset del meccanismo di segmentazione che si traduce in Indirizzo lineare dopo la segmentazione e quindi in Indirizzo fisico dopo il paging. Gli indirizzi fisici sono effettivamente indirizzati dal tuo ram.

       Selector  +--------------+         +-----------+
      ---------->|              |         |           |
                 | Segmentation | ------->|  Paging   |
        Offset   |  Mechanism   |         | Mechanism |
      ---------->|              |         |           |
                 +--------------+         +-----------+
        Virtual                   Linear                Physical
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.