2012-05-03 4 views
6

Sto imparando C, principalmente da K & R, ma ora ho trovato un tutorial in formato pdf per C oggetto e sono affascinato. Lo sto esaminando, ma le mie capacità/conoscenze C potrebbero non essere all'altezza del compito. Questo è il tutorial: http://www.planetpdf.com/codecuts/pdfs/ooc.pdfPuntatori void per strutturare i puntatori in C

La mia domanda deriva dall'osservazione di molte funzioni diverse nella prima coppia di capitoli del pdf. Di seguito è uno di loro (pagina 14 del pdf)

void delete(void * self){ 
    const struct Class ** cp = self; 

    if (self&&*cp&&(*cp)->dtor) 
       self = (*cp)->dtor(self); 
    free(self); 

} 

dtor è un puntatore di funzione distruttore. Ma la conoscenza di questo non è veramente necessaria per le mie domande.

  • La mia prima domanda è, perché è ** cp costante? È necessario o semplicemente essere completo in modo che lo scrittore del codice non faccia nulla di dannoso per caso?
  • In secondo luogo, perché cp è un puntatore-a-un-puntatore (doppio asterisco?). La classe struct è stata definita a pagina 12 del pdf. Non capisco perché non possa essere un singolo puntatore, dato che stiamo lanciando il puntatore automatico a un puntatore Class, a quanto pare.
  • In terzo luogo, in che modo un puntatore void viene modificato in un puntatore di classe (o puntatore a puntatore alla classe)? Penso che questa domanda mostri la mia mancanza di comprensione di C. Quello che immagino nella mia testa è un puntatore vuoto che occupa una certa quantità di memoria, ma deve essere inferiore al puntatore Class, perché una Classe ha un sacco di "roba" dentro. So che un puntatore vuoto può essere "lanciato" su un altro tipo di puntatore, ma non capisco come, dal momento che potrebbe non esserci abbastanza memoria per eseguire questo.

Grazie in anticipo

+3

@Joe Assolutamente nulla di sbagliato in questo. Buone capacità di programmazione in C sono così preziose e difficili da trovare in questi giorni di rapide sceneggiature. – jman

+0

@Joe - Suppongo significhi ANSI C89, di cui si discute il nuovo libro di K & R. il codice nell'esempio non è K & R C IIRC. – Flexo

+0

Sto leggendo K & R ANSI (penso dal 1989, come hai affermato). L'esempio di codice viene dal pdf OOC –

risposta

2

Interessante pdf.

La mia prima domanda è, perché è ** cp costante? È necessario o semplicemente essere accurato in modo che lo scrittore di codice non faccia nulla di dannoso per l'incidente ?

E 'necessario quindi lo scrittore non fa nulla per caso, sì, e per comunicare qualcosa sulla natura del puntatore e il suo utilizzo per il lettore di del codice.

In secondo luogo, perché cp è un puntatore-a-un-puntatore (doppio asterisco?). La classe struct è stata definita a pagina 12 del pdf. Non capisco perché non può essere un puntatore singolo, dal momento che stiamo lanciando il puntatore automatico a un puntatore di classe, a quanto pare.

Date un'occhiata alla definizione di new() (pg 13) in cui viene creato il puntatore p (lo stesso puntatore che viene passato come self-delete()):

void * new (const void * _class, ...) 
{ 
    const struct Class * class = _class; 
    void * p = calloc(1, class —> size); 
    * (const struct Class **) p = class; 

Quindi, 'p' è allocato spazio, poi dereferenziato e assegnato un valore puntatore (l'indirizzo in classe, questo è come il dereferenziamento e l'assegnazione a un puntatore int, ma invece di un int, stiamo assegnando un indirizzo). Ciò significa che la prima cosa in p è un puntatore alla sua definizione di classe. Tuttavia, a p è stato assegnato spazio per più di quello (terrà anche i dati di istanza dell'oggetto). Consideriamo ora delete() ancora:

const struct Class ** cp = self; 
if (self&&*cp&&(*cp)->dtor) 

Quando cp è dereferenziato, dato che era un puntatore a un puntatore, ora è un puntatore. Cosa contiene un puntatore? Un indirizzo. Quale indirizzo? Il puntatore alla definizione di classe che si trova all'inizio del blocco puntato da p.

Questo è un po 'intelligente, perché p non è realmente un puntatore a un puntatore - ha una porzione più grande di memoria allocata che contiene i dati dell'oggetto specifici. Tuttavia, all'inizio di quel blocco c'è un indirizzo (l'indirizzo della definizione della classe), quindi se p è dereferenziato in un puntatore (tramite casting o cp), hai accesso a quella definizione. Quindi, la definizione della classe esiste solo in un posto, ma ogni istanza di quella classe contiene un riferimento alla definizione. Ha senso?Sarebbe più chiaro se p stata scritta come una struttura come questa:

struct object { 
    struct class *class; 
    [...] 
}; 

Poi si potrebbe utilizzare qualcosa come p->class->dtor() al posto del codice esistente in delete(). Tuttavia, questo potrebbe rovinare e complicare l'immagine più grande.

In terzo luogo, in che modo un puntatore di vuoti viene modificato in un puntatore di classe (o puntatore del puntatore a puntatore di classe )? Penso che questa domanda most mostri la mia mancanza di di comprensione di C. Quello che immagino nella mia testa è un puntatore vuoto occupando una certa quantità di memoria, ma deve essere inferiore al puntatore Classe , perché una Classe ha un sacco di "roba" in esso.

Un puntatore è come un int - ha una piccola dimensione impostata per contenere un valore. Quel valore è un indirizzo di memoria. Quando si dereferenzia un puntatore (tramite * o ->), ciò a cui si accede è la memoria a quell'indirizzo. Ma dal momento che gli indirizzi di memoria sono tutti della stessa lunghezza (ad esempio, 8 byte su un sistema a 64 bit) i puntatori stessi hanno le stesse dimensioni indipendentemente dal tipo. È così che ha funzionato la magia del puntatore di oggetti "p". Per ripetere: la prima cosa nel blocco di memoria p è un indirizzo, che gli permette di funzionare come un puntatore a un puntatore, e quando questo è dereferenziato, si ottiene il blocco di memoria contenente la definizione di classe, che è separato dai dati di istanza in p.

0
  1. const è usato per causare un errore di compilazione se il codice tenta di cambiare nulla all'interno dell'oggetto puntato. Questa è una funzione di sicurezza quando il programmatore intende solo leggere l'oggetto e non intende modificarlo.

  2. ** viene utilizzato perché deve essere ciò che è stato passato alla funzione. Sarebbe un grave errore di programmazione ripeterlo come qualcosa che non è.

  3. Un puntatore è semplicemente un indirizzo. Su quasi tutte le moderne CPU, tutti gli indirizzi hanno le stesse dimensioni (32 bit o 64 bit). La modifica di un puntatore da un tipo all'altro non modifica in realtà il valore. Dice di considerare ciò che è a quell'indirizzo come un diverso layout di dati.

1
  1. In questo caso, che è solo una precauzione. La funzione non dovrebbe modificare la classe (in effetti, nulla dovrebbe probabilmente), quindi la trasmissione a const struct Class * fa in modo che la classe sia più difficile da modificare inavvertitamente.

  2. Non ho molta familiarità con la libreria C orientata agli oggetti utilizzata qui, ma sospetto che questo sia un brutto trucco. Il primo puntatore in self è probabilmente un riferimento alla classe, pertanto il dereferenziazione self assegnerà un puntatore alla classe. In effetti, self può sempre essere trattato come struct Class **.

    Un diagramma può aiutare qui:

     +--------+ 
    self -> | *class | -> [Class] 
         | .... | 
         | .... | 
         +--------+ 
    
  3. Ricordare che tutti i puntatori sono solo Indirizzi * Il tipo di un puntatore non incide sulle dimensioni del puntatore;. hanno una larghezza di 32 o 64 bit, a seconda del sistema, in modo da poter convertire da un tipo all'altro in qualsiasi momento. Il compilatore ti avviserà se provi a convertire tra tipi di puntatore senza cast, ma i puntatori void * possono sempre essere convertiti in qualsiasi cosa senza cast, poiché vengono utilizzati in C per indicare un puntatore "generico".

*: Ci sono alcune piattaforme strane in cui questo non è vero, e diversi tipi di puntatori sono in realtà a volte dimensioni diverse. Se stai usando uno di questi, però, lo sapresti. Con ogni probabilità, non lo sei.