Qual è il "tipo" di dati che i puntatori contengono nel linguaggio C?

29

So che i puntatori contengono indirizzi. So che i tipi di puntatori sono "generalmente" conosciuti in base al "tipo" di dati a cui puntano. Ma i puntatori sono ancora variabili e gli indirizzi che contengono devono avere un "tipo" di dati. Secondo le mie informazioni, gli indirizzi sono in formato esadecimale. Ma, ancora non so quale "tipo" di dati è questo esadecimale. (Nota che so cos'è un esadecimale, ma quando dici 10CBA20 , ad esempio, è questa stringa di caratteri? Interi? Cosa? Quando voglio accedere all'indirizzo e manipolarlo .. stesso, ho bisogno di sapere il suo tipo. Questo è il motivo per cui ti sto chiedendo.)

    
posta Gold_Sky 09.06.2015 - 09:21
fonte

10 risposte

64

Il tipo di variabile del puntatore è .. puntatore.

Le operazioni che si sono formalmente autorizzati a fare in C sono di confrontarlo (con altri puntatori, o il valore speciale NULL / zero), per aggiungere o sottrarre interi, o per lanciarlo su altri puntatori.

Una volta accettato il comportamento indefinito , puoi verificare quale sia effettivamente il valore. Di solito è una parola macchina, lo stesso tipo di cosa di un intero e solitamente può essere lanciata senza perdita da e verso un tipo intero. (Un sacco di codice di Windows fa questo nascondendo i puntatori nei typedef di tipo DWORD o HANDLE).

Ci sono alcune architetture in cui i puntatori non sono semplici perché la memoria non è piatta. DOS / 8086 "vicino" e "lontano"; I diversi spazi di memoria e codice del PIC.

    
risposta data 09.06.2015 - 13:14
fonte
43

Stai complicando le cose.

Gli indirizzi sono solo numeri interi, punto. Idealmente sono il numero della cella di memoria referenziata (in pratica questo diventa più complicato a causa di segmenti, memoria virtuale, ecc.).

La sintassi esadecimale è una finzione completa che esiste solo per la comodità dei programmatori. 0x1A e 26 sono esattamente lo stesso numero di esattamente lo stesso tipo , e nessuno dei due utilizza il computer: internamente, il computer usa sempre 00011010 (una serie di segnali binari).

Indipendentemente dal fatto che un compilatore ti permetta di trattare i puntatori in quanto i numeri dipendono dalla definizione della lingua - le lingue di "programmazione dei sistemi" sono tradizionalmente più trasparenti su come funzionano le cose sotto il cofano, mentre "alto livello "le lingue più spesso cercano di nascondere il bare metal dal programmatore - ma questo non cambia nulla sul fatto che i puntatori sono numeri, e di solito il tipo più comune di numero (quello con tanti bit dell'architettura del processore).

    
risposta data 09.06.2015 - 09:38
fonte
15

Un puntatore è proprio questo: un puntatore. Non è qualcos'altro. Non cercare di pensare che sia qualcos'altro.

In linguaggi come C, C ++ e Objective-C, i puntatori di dati hanno quattro tipi di valori possibili:

  1. Un puntatore può essere l'indirizzo di un oggetto.
  2. Un puntatore può puntare appena oltre l'ultimo elemento di un array.
  3. Un puntatore può essere un puntatore nullo, il che significa che non punta a nulla.
  4. Un puntatore può avere un valore indeterminato, in altre parole è spazzatura e tutto può accadere (comprese cose brutte) se si tenta di usarlo.

Ci sono anche dei puntatori di funzione, che identificano una funzione, oppure sono puntatori di funzioni nulle o hanno un valore indeterminato.

Altri puntatori sono "puntatore ai membri" in C ++. Questi sono sicuramente gli indirizzi di memoria non ! Invece, identificano un membro di qualsiasi istanza di una classe. In Objective-C, si hanno selettori, che sono qualcosa come "puntatore a un metodo di istanza con un determinato nome di metodo e nomi di argomenti". Come un puntatore membro, identifica i tutti i metodi di tutte le classi purché abbiano lo stesso aspetto.

Puoi investigare su come un compilatore specifico implementa i puntatori, ma questa è una domanda completamente diversa.

    
risposta data 09.06.2015 - 13:25
fonte
9

Un puntatore è un indirizzamento di pattern di bit (che identifica univocamente allo scopo di leggere o scrivere) una parola di memoria nella RAM. Per ragioni storiche e convenzionali l'unità di aggiornamento è di otto bit, conosciuta in inglese come "byte" o in francese, piuttosto più logicamente, come un ottetto. Questo è onnipresente ma non inerente; altre dimensioni sono esistite.

Se ricordo bene c'era un computer che usava una parola a 29 bit; non solo non è un potere di due, è addirittura primo. Pensavo che fosse SILLIAC ma l'articolo di Wikipedia pertinente non supporta questo. CAN BUS utilizza indirizzi a 29 bit, ma per convenzione gli indirizzi di rete non sono indicati come puntatori anche se sono identici dal punto di vista funzionale.

Le persone continuano ad affermare che i puntatori sono numeri interi. Questo non è né intrinseco né essenziale, ma se interpretiamo i bit pattern come numeri interi emerge la qualità utile dell'ordinalità, permettendo un'implementazione molto diretta (e quindi efficiente su hardware piccolo) di costrutti come "string" e " matrice". La nozione di memoria contigua dipende dall'adiacenza ordinale e il posizionamento relativo è possibile; le operazioni di confronto e aritmetiche su interi possono essere applicate in modo significativo. Per questo motivo esiste quasi sempre una strong correlazione tra la dimensione della parola per l'indirizzamento della memoria e l'ALU (la cosa che fa matematica intera).

A volte i due non corrispondono. Nei primi PC il bus degli indirizzi era largo 24 bit.

    
risposta data 09.06.2015 - 16:14
fonte
6

Fondamentalmente ogni computer moderno è una macchina che spinge un po '. Solitamente spinge bit in cluster di dati, chiamati byte, parole, parole o qword.

Un byte è composto da 8 bit, una parola 2 byte (o 16 bit), una parola dword 2 (o 32 bit) e una parola dword 2 dwords (o 64 bit). Questi non sono l'unico modo per organizzare i bit. Anche la manipolazione a 128 bit e 256 bit si verifica, spesso nelle istruzioni SIMD.

Le istruzioni di assemblaggio funzionano su registri e gli indirizzi di memoria di solito operano in uno dei moduli sopra.

ALU (unità logiche aritmetiche) operano su tali pacchetti di bit come se rappresentassero numeri interi (di solito il formato Complemento di due) e FPU come se fossero valori in virgola mobile (di solito in stile IEEE 754 float e double ) . Altre parti agiranno come se fossero dati in bundle di alcuni formati, caratteri, voci di tabelle, istruzioni della CPU o indirizzi.

Su un tipico computer a 64 bit, i pacchetti di 8 byte (64 bit) sono indirizzi. Visualizziamo questi indirizzi convenzionalmente come in un formato esadecimale (come 0xabcd1234cdef5678 ), ma questo è solo un modo semplice per gli umani di leggere i pattern di bit. Ogni byte (8 bit) è scritto come due caratteri esadecimali (equivalentemente ciascun carattere esadecimale - da 0 a F - rappresenta 4 bit).

Ciò che sta effettivamente accadendo (per un certo livello di realtà) è che ci sono bit, di solito memorizzati in un registro o memorizzati in posizioni adiacenti in un banco di memoria, e stiamo solo cercando di descriverli a un altro umano.

Il puntatore consiste nel chiedere al controller di memoria di fornirci alcuni dati in quella posizione. Normalmente chiedi al controller di memoria un determinato numero di byte in una determinata posizione (beh, implicitamente in una serie di posizioni, di solito contigue) e viene fornito attraverso vari meccanismi in cui non entrerò.

Il codice di solito specifica una destinazione per i dati da recuperare - un registro, un altro indirizzo di memoria, ecc. e di solito è una cattiva idea caricare dati in virgola mobile in un registro che si aspetta un intero, o viceversa .

Il tipo di dati in C / C ++ è qualcosa di cui il compilatore tiene traccia e modifica il codice generato. Di solito non c'è nulla di intrinseco nei dati che lo renda effettivamente di qualsiasi tipo. Solo una raccolta di bit (disposti in byte) che vengono manipolati in modo intero (o in modo simile al float o in un modo simile a un indirizzo) dal codice.

Ci sono delle eccezioni a questo. Ci sono architetture in cui certe cose sono un tipo diverso di bit. L'esempio più comune sono le pagine di esecuzione protette - mentre le istruzioni che dicono alla CPU che cosa fanno sono bit, in fase di esecuzione le pagine (di memoria) contenenti codice da eseguire sono contrassegnate appositamente, non possono essere modificate e non è possibile eseguire pagine che non sono marcate come pagine di esecuzione.

Ci sono anche dati di sola lettura (a volte memorizzati nella ROM che non possono essere scritti fisicamente!), problemi di allineamento (alcuni processori non possono caricare double s dalla memoria a meno che non siano allineati in particolari modi o istruzioni SIMD che richiedono certe allineamento) e miriadi di altre stranezze di architettura.

Anche il livello di dettaglio di cui sopra è una bugia. I computer non "spingono" realmente i bit, stanno davvero spintonando tensioni e corrente. Queste tensioni e correnti a volte non fanno ciò che si suppone debbano fare a livello di astrazione dei bit. I chip sono progettati per rilevare la maggior parte di tali errori e correggerli senza che l'astrazione di livello superiore debba esserne a conoscenza.

Anche quella è una bugia.

Ogni livello di astrazione nasconde quello sottostante e ti consente di pensare a risolvere i problemi senza dover tenere a mente i diagrammi di Feynman per stampare "Hello World" .

Quindi, a un livello sufficiente di onestà, i computer spingono i bit e a questi bit viene dato un significato in base al modo in cui vengono utilizzati.

    
risposta data 09.06.2015 - 17:23
fonte
3

Le persone hanno fatto un grosso sforzo sul fatto che i puntatori siano numeri interi o meno. Ci sono in realtà risposte a queste domande. Tuttavia, dovrai fare un passo nella terra delle specifiche, che non è per i deboli di cuore. Daremo un'occhiata alle specifiche C, ISO / IEC 9899: TC2

6.3.2.3 Puntatori

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

  2. Qualsiasi tipo di puntatore può essere convertito in un tipo intero. Ad eccezione di quanto precedentemente specificato, il il risultato è definito dall'implementazione. Se il risultato non può essere rappresentato nel tipo intero, il comportamento non è definito. Il risultato non deve essere compreso nell'intervallo di valori di qualsiasi numero intero tipo.

Ora, per questo, avrai bisogno di conoscere alcuni termini specifici delle specifiche. "implementazione definita" significa che ogni singolo compilatore può definirlo in modo diverso. In effetti, un compilatore può persino definirlo in modi diversi a seconda delle tue impostazioni del compilatore. Comportamento indefinito significa che il compilatore è autorizzato a fare assolutamente qualsiasi cosa, dal dare un errore di tempo di compilazione a comportamenti inspiegabili, a lavorare perfettamente.

Da questo possiamo vedere che il modulo di archiviazione sottostante non è specificato, a parte il fatto che può essere una conversione in un tipo intero. Ora, a dire il vero, praticamente ogni compilatore sotto il sole rappresenta i puntatori sotto il cappuccio come indirizzi interi (con una manciata di casi speciali in cui potrebbe essere rappresentato come 2 interi anziché solo 1), ma la specifica permette assolutamente nulla, come rappresentare indirizzi come una stringa di 10 caratteri!

Se avanza velocemente da C e osserviamo le specifiche C ++, otteniamo un po 'più di chiarezza con reinterpret_cast , ma questa è una lingua diversa, quindi il suo valore per te può variare:

ISO / IEC N337: specifiche di bozza C ++ 11 (ho solo la bozza a portata di mano)

5.2.10 Reinterpretazione del cast

  1. Un puntatore può essere convertito in modo esplicito su qualsiasi tipo di integrale abbastanza grande da trattenerlo. La funzione di mappatura è definita dall'implementazione. [Nota: si intende che non sorprenda coloro che conoscono la struttura di indirizzamento della macchina sottostante. -End note] Un valore di tipo std :: nullptr_t può essere convertito in un tipo integrale; la conversione ha lo stesso significato e validità di una conversione di (void *) 0 al tipo integrale. [Nota: reinterpret_cast non può essere utilizzato per convertire un valore di qualsiasi tipo nel tipo std :: nullptr_t . -End note]

  2. Un valore di tipo integrale o tipo di enumerazione può essere convertito esplicitamente in un puntatore. Un puntatore convertito in un numero intero di dimensioni sufficienti (se ne esiste uno nell'implementazione) e di nuovo nello stesso tipo di puntatore avrà il suo valore originale; i mapping tra i puntatori e gli interi sono altrimenti definiti dall'implementazione. [Nota: ad eccezione di quanto descritto in 3.7.4.3, il risultato di tale conversione non sarà un puntatore sicuro valore. -End note]

Come potete vedere qui, con qualche anno in più, C ++ ha scoperto che era sicuro assumere che esistesse una mappatura agli interi, quindi non si parla più di comportamento indefinito (sebbene ci sia un'interessante contraddizione tra parti 4 e 5 con il fraseggio "se ne esiste uno sull'implementazione")

Ora cosa dovresti togliere da questo?

  • L'esatta rappresentazione dei puntatori è definita dall'implementazione. (infatti, solo per renderlo più incisivo, alcuni piccoli computer integrati rappresentano il puntatore nullo, (void ) 0, come indirizzo 255 per supportare alcuni trucchi di aliasing degli indirizzi che usano) *
  • Se devi chiedere informazioni sulla rappresentazione dei puntatori nella memoria, probabilmente non sei a un punto della tua carriera di programmatore in cui vuoi giocherellare con loro.

La migliore scommessa: lanciare su un (char *). Le specifiche C e C ++ sono piene di regole che specificano il packing di matrici e strutture, ed entrambe consentono sempre il cast di qualsiasi puntatore a un char *. char è sempre 1 byte (non è garantito in C, ma da C ++ 11 è diventato una parte obbligatoria del linguaggio, quindi è relativamente sicuro presumere che sia 1 byte in tutto il mondo). Questo ti permette di fare un po 'di aritmetica dei puntatori a livello di byte per byte senza ricorrere alla necessità di conoscere le rappresentazioni specifiche dei puntatori.

    
risposta data 10.06.2015 - 07:46
fonte
1

Sulla maggior parte delle architetture, il tipo di puntatore cessa di esistere una volta che sono stati tradotti in codice macchina (eccetto forse i "fat pointer"). Pertanto, un puntatore a int non è distinguibile da un puntatore a double , almeno da solo. *

[*] Sebbene tu possa ancora fare supposizioni basate sui tipi di operazioni che ti vengono applicate.

    
risposta data 09.06.2015 - 10:24
fonte
1

Una cosa importante da capire su C e C ++ è che tipi sono effettivamente. Tutto quello che fanno veramente è indicare al compilatore come interpretare un insieme di bit / byte. Iniziamo con il seguente codice:

int var = -1337;

A seconda dell'architettura, a un intero di solito vengono dati 32 bit di spazio per memorizzare quel valore. Ciò significa che lo spazio nella memoria in cui var è memorizzato sarà simile a "11111111 11111111 11111010 11000111" o in hex "0xFFFFFAC7". Questo è tutto. Questo è tutto ciò che è memorizzato in quella posizione. Tutti i tipi fanno dire al compilatore come interpretare quell'informazione. I puntatori non sono diversi. Se faccio qualcosa del genere:

int* var_ptr = &var;   //the ampersand is telling C "get the address where var's value is located"

Quindi il compilatore otterrà la posizione di var e quindi memorizzerà quell'indirizzo nello stesso modo in cui il primo snippet di codice salva il valore -1337. Non c'è differenza nel modo in cui vengono memorizzati, solo nel modo in cui vengono utilizzati. Non importa nemmeno che ho fatto var_ptr un puntatore a un int. Se lo volessi, potresti farlo.

unsigned int var2 = *(unsigned int*)var_ptr;

Questo copierà il valore esadecimale sopra riportato di var (0xFFFFFAC7) nella posizione in cui è memorizzato il valore di var2. Se dovessimo usare var2, troveremmo che il valore sarebbe 4294965959. I byte in var2 sono uguali a var, ma il valore numerico differisce. Il compilatore li ha interpretati in modo diverso perché gli abbiamo detto che quei bit rappresentano un lungo non firmato. Puoi fare lo stesso anche per il valore del puntatore.

unsigned int var3 = (unsigned int)var_ptr;

Finiresti per interpretare il valore che rappresenta l'indirizzo di var come int unsigned in questo esempio.

Spero che questo chiarisca le cose per te e ti fornisca una visione migliore di come funziona C. Ti preghiamo di notare che NON DEVE fare nessuna delle cose pazze che ho fatto nelle due righe sottostanti nel codice di produzione attuale. Era solo per dimostrazione.

    
risposta data 09.06.2015 - 18:41
fonte
1

Integer.

Lo spazio degli indirizzi in un computer è numerato in sequenza, a partire da 0 e incrementato di 1. Quindi un puntatore contiene un numero intero che corrisponde a un indirizzo nello spazio degli indirizzi.

    
risposta data 10.06.2015 - 08:49
fonte
1

Tipi combinati.

In particolare, alcuni tipi si combinano, quasi come se fossero parametrizzati con segnaposto. I tipi di matrice e puntatore sono come questo; hanno uno di questi segnaposto, che è il tipo dell'elemento dell'array o della cosa puntata, rispettivamente. Anche i tipi di funzioni sono come questo; possono avere più segnaposto per i parametri e un segnaposto per il tipo di reso.

Una variabile dichiarata per contenere un puntatore a char ha tipo "pointer to char". Una variabile dichiarata per contenere un puntatore a puntatore a int ha tipo "puntatore a puntatore su int".

Un (valore di) tipo "puntatore a puntatore a int" può essere cambiato in "puntatore a int" con un'operazione di dereferenziazione. Quindi, la nozione di tipo non è solo parole, ma un costrutto matematicamente significativo, che stabilisce cosa possiamo fare con valori del tipo (come dereferenziamento o passare come parametro o assegnare alla variabile, ma determina anche la dimensione (numero di byte) di indicizzazione, aritmetica e operazioni di incremento / decremento).

P.S. Se vuoi approfondire i tipi, prova questo blog: link

    
risposta data 10.06.2015 - 02:47
fonte

Leggi altre domande sui tag