Domanda

Questa domanda è rivolta ai guru C là fuori:

In C, è possibile dichiarare un puntatore come segue:

char (* p)[10];

.. che sostanzialmente afferma che questo puntatore punta a un array di 10 caratteri. La cosa bella di dichiarare un puntatore come questo è che si otterrà un errore di compilazione se si tenta di assegnare un puntatore di un array di dimensioni diverse a p. Ti darà anche un errore di tempo di compilazione se provi ad assegnare il valore di un semplice puntatore a carattere a p. Ho provato questo con gcc e sembra funzionare con ANSI, C89 e C99.

Mi sembra che dichiarare un puntatore come questo sarebbe molto utile, in particolare quando si passa un puntatore a una funzione. Di solito, le persone scrivono il prototipo di una tale funzione come questa:

void foo(char * p, int plen);

Se ti aspettavi un buffer di dimensioni specifiche, testeresti semplicemente il valore di plen. Tuttavia, non si può essere certi che la persona che ti passa p fornirà realmente posizioni di memoria valide in quel buffer. Devi fidarti che la persona che ha chiamato questa funzione sta facendo la cosa giusta. D'altra parte:

void foo(char (*p)[10]);

.. costringerebbe il chiamante a fornire un buffer delle dimensioni specificate.

Sembra molto utile, ma non ho mai visto un puntatore dichiarato così in nessun codice che abbia mai incontrato.

La mia domanda è: c'è qualche motivo per cui le persone non dichiarano puntatori come questo? Non vedo qualche insidia evidente?

È stato utile?

Soluzione 2

Vorrei aggiungere la risposta di AndreyT (nel caso qualcuno si imbattesse in questa pagina alla ricerca di maggiori informazioni su questo argomento):

Mentre comincio a giocare di più con queste dichiarazioni, mi rendo conto che c'è un grande handicap associato a loro in C (apparentemente non in C ++). È abbastanza comune avere una situazione in cui si desidera dare a un chiamante un puntatore const a un buffer in cui si è scritto. Sfortunatamente, questo non è possibile quando si dichiara un puntatore come questo in C. In altre parole, lo standard C (6.7.3 - Paragrafo 8) è in contrasto con qualcosa del genere:


   int array[9];

   const int (* p2)[9] = &array;  /* Not legal unless array is const as well */

Questo vincolo non sembra essere presente in C ++, rendendo questo tipo di dichiarazioni molto più utile. Ma nel caso di C, è necessario ricorrere a una normale dichiarazione del puntatore ogni volta che si desidera un puntatore const al buffer di dimensioni fisse (a meno che il buffer stesso non sia stato dichiarato const per iniziare). Puoi trovare maggiori informazioni in questo thread di posta: testo del link

Questo è un grave vincolo secondo me e potrebbe essere uno dei motivi principali per cui le persone di solito non dichiarano puntatori come questo in C. L'altro è il fatto che la maggior parte delle persone non sa nemmeno che puoi dichiarare un puntatore come ha sottolineato AndreyT.

Altri suggerimenti

Quello che stai dicendo nel tuo post è assolutamente corretto. Direi che ogni sviluppatore C arriva esattamente alla stessa scoperta e alla stessa conclusione quando (se) raggiungono un certo livello di competenza con il linguaggio C.

Quando le specifiche dell'area dell'applicazione richiedono una matrice di dimensioni fisse specifiche (la dimensione della matrice è una costante di tempo di compilazione), l'unico modo corretto per passare tale matrice a una funzione è utilizzare un puntatore a matrice parametro

void foo(char (*p)[10]);

(in linguaggio C ++ questo viene fatto anche con riferimenti

void foo(char (&p)[10]);

).

Ciò consentirà il controllo del tipo a livello di lingua, che assicurerà che l'array di dimensioni esatte sia fornito come argomento. In effetti, in molti casi le persone usano questa tecnica implicitamente, senza nemmeno rendersene conto, nascondendo il tipo di array dietro un nome typedef

typedef int Vector3d[3];

void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);

Nota inoltre che il codice sopra è invariante rispetto al tipo Vector3d che è un array o un struct . Puoi cambiare la definizione di Vector3d in qualsiasi momento da un array a struct e viceversa, e non dovrai cambiare la dichiarazione di funzione. In entrambi i casi le funzioni riceveranno un oggetto aggregato "per riferimento" (ci sono eccezioni a questo, ma nel contesto di questa discussione questo è vero).

Tuttavia, non vedrai questo metodo di passaggio dell'array usato esplicitamente troppo spesso, semplicemente perché troppe persone vengono confuse da una sintassi piuttosto contorta e semplicemente non sono abbastanza a proprio agio con tali funzionalità del linguaggio C da usarle correttamente. Per questo motivo, nella vita reale media, passare un array come puntatore al suo primo elemento è un approccio più popolare. Sembra solo " più semplice " ;.

Ma in realtà, usare il puntatore al primo elemento per il passaggio di array è una tecnica di nicchia, un trucco, che ha uno scopo molto specifico: il suo unico scopo è quello di facilitare il passaggio di matrici di dimensioni diverse (ovvero dimensione del tempo di esecuzione). Se devi davvero essere in grado di elaborare array di dimensioni di runtime, il modo corretto per passare un array di questo tipo è tramite un puntatore al suo primo elemento con le dimensioni del calcestruzzo fornite da un parametro aggiuntivo

void foo(char p[], unsigned plen);

In realtà, in molti casi è molto utile essere in grado di elaborare array di dimensioni di runtime, il che contribuisce anche alla popolarità del metodo. Molti sviluppatori C semplicemente non incontrano (o non riconoscono mai) la necessità di elaborare un array di dimensioni fisse, rimanendo così ignari della corretta tecnica di dimensioni fisse.

Tuttavia, se la dimensione dell'array è fissa, passandola come puntatore a un elemento

void foo(char p[])

è un grave errore a livello di tecnica, che sfortunatamente è piuttosto diffuso in questi giorni. Una tecnica pointer-to-array è un approccio molto migliore in questi casi.

Un altro motivo che potrebbe ostacolare l'adozione della tecnica di passaggio di array di dimensioni fisse è il dominio dell'approccio ingenuo alla tipizzazione di array allocati dinamicamente. Ad esempio, se il programma richiede array fissi di tipo char [10] (come nel tuo esempio), uno sviluppatore medio malloc come array

char *p = malloc(10 * sizeof *p);

Questo array non può essere passato a una funzione dichiarata come

char (*p)[10] = malloc(sizeof *p);

che confonde lo sviluppatore medio e li fa abbandonare la dichiarazione di parametro di dimensioni fisse senza pensarci ancora. In realtà, tuttavia, la radice del problema risiede nell'approccio ingenuo malloc . Il formato malloc mostrato sopra dovrebbe essere riservato per array di dimensioni di runtime. Se il tipo di array ha dimensioni in fase di compilazione, un modo migliore per malloc sarebbe il seguente

foo(p);

Questo, ovviamente, può essere facilmente passato al foo sopra dichiarato

<*>

e il compilatore eseguirà il controllo del tipo corretto. Ma ancora una volta, questo è troppo confuso per uno sviluppatore C non preparato, motivo per cui non lo vedrai troppo spesso nel "tipico" codice medio giornaliero.

Il motivo ovvio è che questo codice non viene compilato:

extern void foo(char (*p)[10]);
void bar() {
  char p[10];
  foo(p);
}

La promozione predefinita di un array è verso un puntatore non qualificato.

Vedi anche questa domanda , usando foo (& amp; p) dovrebbe funzionare.

Bene, in poche parole, C non fa le cose in quel modo. Un array di tipo T viene passato come puntatore al primo T nell'array, ed è tutto ciò che ottieni.

Ciò consente alcuni algoritmi interessanti ed eleganti, come il looping dell'array con espressioni come

*dst++ = *src++

Il rovescio della medaglia è che la gestione delle dimensioni dipende da te. Sfortunatamente, la mancata osservanza coscienziosa ha anche portato a milioni di bug nella codifica C e / o opportunità di sfruttamento malevolo.

Ciò che si avvicina a ciò che chiedi in C è passare un struct (per valore) o un puntatore a uno (per riferimento). Finché viene utilizzato lo stesso tipo di struttura su entrambi i lati di questa operazione, sia il codice che distribuisce il riferimento che il codice che lo utilizza sono in accordo con la dimensione dei dati gestiti.

La tua struttura può contenere qualunque dato tu voglia; potrebbe contenere un array di dimensioni ben definite.

Tuttavia, nulla impedisce a te o a un programmatore incompetente o malevolo di usare i cast per ingannare il compilatore nel trattare la tua struttura come di dimensioni diverse. La capacità quasi libera di fare questo tipo di cose fa parte del design di C.

Puoi dichiarare una serie di caratteri in vari modi:

char p[10];
char* p = (char*)malloc(10 * sizeof(char));

Il prototipo di una funzione che accetta un array in base al valore è:

void foo(char* p); //cannot modify p

o per riferimento:

void foo(char** p); //can modify p, derefernce by *p[0] = 'f';

o dalla sintassi dell'array:

void foo(char p[]); //same as char*

Non consiglierei questa soluzione

typedef int Vector3d[3];

poiché oscura il fatto che Vector3D ha un tipo che tu deve sapere. I programmatori di solito non si aspettano variabili del stesso tipo per avere dimensioni diverse. Considera:

void foo(Vector3d a) {
   Vector3D b;
}

dove sizeof a! = sizeof b

Voglio anche usare questa sintassi per abilitare un maggior controllo del tipo.

Ma concordo anche sul fatto che la sintassi e il modello mentale dell'uso dei puntatori è più semplice e più facile da ricordare.

Ecco alcuni altri ostacoli che ho incontrato.

  • Per accedere all'array è necessario utilizzare (* p) [] :

    void foo(char (*p)[10])
    {
        char c = (*p)[3];
        (*p)[0] = 1;
    }
    

    È invece allettante utilizzare un puntatore a carattere locale:

    void foo(char (*p)[10])
    {
        char *cp = (char *)p;
        char c = cp[3];
        cp[0] = 1;
    }
    

    Ma questo annullerebbe parzialmente lo scopo di usare il tipo corretto.

  • Bisogna ricordare di usare l'indirizzo dell'operatore quando si assegna l'indirizzo di un array a un puntatore a array:

    char a[10];
    char (*p)[10] = &a;
    

    L'operatore address-of ottiene l'indirizzo dell'intero array in & amp; a , con il tipo corretto per assegnarlo a p . Senza l'operatore, a viene automaticamente convertito nell'indirizzo del primo elemento dell'array, come in & amp; a [0] , che ha un tipo diverso.

    Dato che questa conversione automatica è già in corso, sono sempre perplesso che sia necessario & amp; . È coerente con l'uso di & amp; su variabili di altri tipi, ma devo ricordare che un array è speciale e che ho bisogno di & amp; per ottenere il corretto tipo di indirizzo, anche se il valore dell'indirizzo è lo stesso.

    Uno dei motivi del mio problema potrebbe essere che ho imparato K & amp; RC negli anni '80, il che non mi consentiva ancora di usare l'operatore & amp; su interi array (anche se alcuni compilatori lo ignoravano o tolleravano sintassi). Il che, tra l'altro, potrebbe essere un altro motivo per cui i puntatori agli array hanno difficoltà a essere adottati: funzionano correttamente solo da ANSI C e la limitazione dell'operatore & amp; potrebbe essere stata un altro motivo per considerarli troppo imbarazzanti.

  • Quando typedef è non utilizzato per creare un tipo per il puntatore a array (in un file di intestazione comune), quindi un puntatore globale a -array ha bisogno di una dichiarazione extern più complicata per condividerla tra i file:

    fileA:
    char (*p)[10];
    
    fileB:
    extern char (*p)[10];
    

Forse mi manca qualcosa, ma ... dal momento che le matrici sono puntatori costanti, in pratica ciò significa che non ha senso passare attorno ai puntatori.

Non potresti semplicemente usare void foo (char p [10], int plen); ?

Sul mio compilatore (vs2008) tratta char (* p) [10] come una matrice di puntatori di caratteri, come se non ci fossero parentesi, anche se compilo come file C. Il supporto del compilatore per questa "variabile" è? Se è così, questo è uno dei motivi principali per non usarlo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top