Perché l'asterisco è prima del nome della variabile, piuttosto che dopo il tipo?


187

Perché la maggior parte dei programmatori C nomina variabili come questa:

int *myVariable;

piuttosto che in questo modo:

int* myVariable;

Entrambi sono validi Mi sembra che l'asterisco sia una parte del tipo, non una parte del nome della variabile. Qualcuno può spiegare questa logica?


1
Il secondo stile sembra più intuitivo in generale, ma il primo è la strada da percorrere per evitare bug relativi al tipo nel codice. Se sei davvero attaccato a quest'ultimo stile, potresti sempre andare avanti typedefs, ma ciò aggiungerà complessità non necessaria, IMHO.
Cloud

Risposte:


251

Sono ESATTAMENTE equivalenti. Tuttavia, in

int *myVariable, myVariable2;

Sembra ovvio che myVariable ha tipo int * , mentre myVariable2 ha tipo int . Nel

int* myVariable, myVariable2;

può sembrare ovvio che entrambi sono di tipo int * , ma ciò non è corretto come myVariable2ha tipo int di .

Pertanto, il primo stile di programmazione è più intuitivo.


135
forse ma non mescolerei e abbinerei i tipi in una dichiarazione.
BobbyShaftoe,

15
@BobbyShaftoe Concordato. Anche dopo aver letto ogni argomento qui, mi attengo ai int* someVarprogetti personali. Ha più senso.
Alyssa Haroldsen,

30
@Kupiakos Ha più senso fino a quando non apprendi la sintassi delle dichiarazioni di C basata su "dichiarazioni follow use". Le dichiarazioni utilizzano esattamente la stessa sintassi utilizzata dalle variabili dello stesso tipo. Quando si dichiara una matrice di INT, non sembra come: int[10] x. Questa semplicemente non è la sintassi di C. La grammatica analizza esplicitamente come:, int (*x)e non come (int *) x, quindi posizionare l'asterisco a sinistra è semplicemente fuorviante e basato su un malinteso sulla sintassi della dichiarazione C.
Peaker,

8
Correzione: pertanto, non si dovrebbe mai dichiarare più di una variabile su una sola riga. In generale, non dovresti motivare un certo stile di codifica basato su altri stili di codifica non correlati, cattivi e pericolosi.
Lundin,

6
Questo è il motivo per cui mi attengo a una variabile per dichiarazione del puntatore. Non c'è assolutamente confusione se lo fai int* myVariable; int myVariable2;invece.
Patrick Roberts,

122

Se lo guardi in un altro modo, *myVariableè di tipo int, il che ha un senso.


6
Questa è la mia spiegazione preferita e funziona bene perché spiega le stranezze della dichiarazione di C in generale - anche la sintassi disgustosa e nodosa del puntatore della funzione.
Benjamin Pollack,

12
È un po 'pulito, dato che puoi immaginare che non ci siano tipi di puntatori reali. Ci sono solo variabili che, se opportunamente referenziate o non referenziate, ti danno uno dei tipi primitivi.
biozinc

1
In realtà, '* myVariable' può essere di tipo NULL. A peggiorare le cose potrebbe essere solo una posizione di memoria casuale.
qonf,

4
qonf: NULL non è un tipo. myVariablepuò essere NULL, nel qual caso *myVariableprovoca un errore di segmentazione, ma non esiste un tipo NULL.
Daniel Roethlisberger,

28
Questo punto può essere fuorviante in tale contesto: int x = 5; int *pointer = &x;perché suggerisce di impostare l'int *pointersu un valore, non su pointerse stesso.
rafalcieslak,

40

Perché il * in quella riga si lega più strettamente alla variabile che al tipo:

int* varA, varB; // This is misleading

Come sottolinea @Lundin di seguito, const aggiunge ancora più sottigliezze a cui pensare. Puoi evitarlo del tutto dichiarando una variabile per riga, che non è mai ambigua:

int* varA;
int varB;

L'equilibrio tra codice chiaro e codice conciso è difficile da trovare: int a;neanche una dozzina di linee ridondanti non va bene. Tuttavia, per impostazione predefinita ho una dichiarazione per riga e mi preoccupo di combinare il codice in un secondo momento.


2
Bene, il primo esempio fuorviante è ai miei occhi un errore di progettazione. Se potessi, rimuoverei completamente questo modo di dichiarazione da C e lo farei in modo che entrambi siano di tipo int *.
Adam Bajger,

1
"il * si lega più strettamente alla variabile che al tipo" Questo è un argomento ingenuo. Prendere in considerazione int *const a, b;. Da dove viene "legato" *? Il tipo di aè int* const, quindi come puoi dire che * appartiene alla variabile quando fa parte del tipo stesso?
Lundin,

1
Specifico per la domanda, o che copre tutti i casi: scegline uno. Farò una nota, ma questo è un altro buon argomento per il mio ultimo suggerimento: una dichiarazione per riga riduce le opportunità di rovinare tutto.
ojrac,

36

Qualcosa che nessuno ha menzionato qui finora è che questo asterisco è in realtà l '" operatore di dereference " in C.

*a = 10;

La riga sopra non significa che voglio assegnare 10a a, significa che voglio assegnare 10a qualunque posizione di memoria apunti. E non ho mai visto nessuno scrivere

* a = 10;

hai? Quindi l' operatore di dereference è praticamente sempre scritto senza uno spazio. Questo è probabilmente per distinguerlo da una moltiplicazione suddivisa su più righe:

x = a * b * c * d
  * e * f * g;

Qui *esarebbe fuorviante, no?

Bene, ora cosa significa effettivamente la seguente riga:

int *a;

Molte persone direbbero:

Significa che aè un puntatore a un intvalore.

Questo è tecnicamente corretto, alla maggior parte delle persone piace vederlo / leggerlo in quel modo ed è così che lo definirebbero i moderni standard C (si noti che il linguaggio C stesso precede tutti gli standard ANSI e ISO). Ma non è l'unico modo per vederlo. Puoi anche leggere questa riga come segue:

Il valore dedotto di aè di tipo int.

Quindi, in realtà, l'asterisco in questa dichiarazione può essere visto come un operatore di dereference, il che spiega anche la sua posizione. E questo aè un puntatore che non è affatto dichiarato, è implicito dal fatto che l'unica cosa che si può effettivamente fare è un puntatore.

Lo standard C definisce solo due significati per l' *operatore:

  • operatore indiretto
  • operatore di moltiplicazione

E l'indirizzamento indiretto è solo un singolo significato, non esiste un significato aggiuntivo per dichiarare un puntatore, c'è solo l'indirizzamento, che è ciò che fa l'operazione di dereference, esegue un accesso indiretto, quindi anche all'interno di un'istruzione come int *a;questa è un accesso indiretto ( *significa accesso indiretto) e quindi la seconda affermazione sopra è molto più vicina allo standard rispetto alla prima.


6
Grazie per avermi salvato dalla scrittura di un'altra risposta qui. A proposito, di solito leggo int a, *b, (*c)();qualcosa del tipo "dichiara i seguenti oggetti come int: l'oggetto a, l'oggetto indicato da be l'oggetto restituito dalla funzione indicata da c".
Antti Haapala,

1
L' *In int *a;non è un operatore e non è la dereferenziazione a(che non è nemmeno stata ancora definita)
MM

1
@MM Indicare la pagina e il numero di riga di qualsiasi standard ISO C in cui questo standard afferma che l'asterisco può essere diverso dalla moltiplicazione o dall'indirizzamento indiretto. Mostra solo "dichiarazione del puntatore" per esempio, non definisce da nessuna parte un terzo significato per asterisco (e non definisce alcun significato, che non sarebbe un operatore). Oh, da nessuna parte ho affermato che qualcosa è "definito", lo hai inventato. O come dice Jonathan Leffler hat, nello standard C, * è sempre "grammatica", non fa parte degli specificatori di dichiarazione elencati (quindi non fa parte di una dichiarazione, quindi deve essere un operatore)
Mecki,

1
@MM 6.7.6.1 mostra solo la dichiarazione con l'esempio, esattamente come ho detto, non dà significato a *(dà solo un senso all'espressione totale, non alla *all'interno dell'espressione!). Dice che "int a;" dichiara un puntatore, cosa che fa, mai rivendicato diversamente, ma senza un significato dato a *, leggendolo come * il valore dedotto di aè un int è ancora totalmente valido, poiché ha lo stesso significato fattuale. Niente, davvero niente di scritto in 6.7.6.1 contraddirebbe questa affermazione.
Mecki,

2
Non esiste "espressione totale". int *a;è una dichiarazione, non un'espressione. anon è segnalato da int *a;. anon esiste nemmeno nel momento in cui *viene elaborato. Pensi che int *a = NULL;sia un bug perché dereferenzia un puntatore nullo?
MM

17

Ho intenzione di andare su un arto qui e dire che non v'è una risposta diretta a questa domanda , sia per le dichiarazioni di variabili e per tipi di parametri e di ritorno, che è che l'asterisco dovrebbe andare vicino al nome: int *myVariable;. Per apprezzare il perché, guarda come dichiari altri tipi di simboli in C:

int my_function(int arg); per una funzione;

float my_array[3] per un array.

Il modello generale, indicato come dichiarazione segue l'uso , è che il tipo di un simbolo è suddiviso nella parte prima del nome, e le parti attorno al nome, e queste parti attorno al nome imitano la sintassi che useresti per ottenere un valore del tipo a sinistra:

int a_return_value = my_function(729);

float an_element = my_array[2];

e: int copy_of_value = *myVariable;.

C ++ lancia una chiave nelle opere con riferimenti, poiché la sintassi nel punto in cui si usano i riferimenti è identica a quella dei tipi di valore, quindi si potrebbe sostenere che C ++ adotta un approccio diverso a C. D'altra parte, C ++ mantiene lo stesso comportamento di C nel caso di puntatori, quindi i riferimenti si distinguono davvero per quello strano in questo senso.


12

Questa è solo una questione di preferenza.

Quando leggi il codice, la distinzione tra variabili e puntatori è più facile nel secondo caso, ma può creare confusione quando inserisci variabili e puntatori di un tipo comune in una singola riga (che a sua volta è spesso scoraggiata dalle linee guida del progetto, perché diminuisce la leggibilità).

Preferisco dichiarare i puntatori con il segno corrispondente accanto al nome del tipo, ad es

int* pMyPointer;

3
La domanda riguarda C, dove non ci sono riferimenti.
Antti Haapala,

Grazie per averlo sottolineato, anche se la domanda non riguardava i puntatori o i riferimenti, ma sostanzialmente la formattazione del codice.
macbirdie,

8

Un grande guru una volta disse: "Leggilo nel modo del compilatore, devi".

http://www.drdobbs.com/conversationsa-midsummer-nights-madness/184403835

È vero che questo era sull'argomento del posizionamento delle costanti, ma la stessa regola si applica qui.

Il compilatore lo legge come:

int (*a);

non come:

(int*) a;

Se prendi l'abitudine di posizionare la stella accanto alla variabile, questo renderà le tue dichiarazioni più facili da leggere. Evita anche le piaghe degli occhi come:

int* a[10];

-- Modificare --

Spiegare esattamente cosa intendo quando dico che è analizzato come int (*a), ciò significa che si *lega più strettamente di aquanto non faccia a int, in modo molto simile all'espressione che si 4 + 3 * 7 3lega più strettamente di 7quanto non faccia a 4causa della maggiore precedenza di *.

Con scuse per l'arte ascii, una sinossi dell'AST per l'analisi int *aappare più o meno così:

      Declaration
      /         \
     /           \
Declaration-      Init-
Secifiers       Declarator-
    |             List
    |               |
    |              ...
  "int"             |
                Declarator
                /       \
               /        ...
           Pointer        \
              |        Identifier
              |            |
             "*"           |
                          "a"

Come è chiaramente mostrato, si *lega più strettamente adal momento che è il loro antenato comune Declarator, mentre è necessario salire fino all'albero Declarationper trovare un antenato comune che coinvolge il int.


No, il compilatore legge sicuramente il tipo come (int*) a.
Lundin

@Lundin Questo è il grande fraintendimento che hanno i programmatori C ++ più moderni. L'analisi di una dichiarazione variabile va in questo modo. Passaggio 1. Leggere il primo token, che diventa il "tipo base" della dichiarazione. intin questo caso. Passaggio 2. Leggere una dichiarazione, comprese eventuali decorazioni di tipo. *ain questo caso. Passaggio 3 Leggi il personaggio successivo. Se la virgola, consumala e torna al passaggio 2. Se il punto e virgola si ferma. Se altro, genera un errore di sintassi. ...
dgnuff,

@Lundin ... Se il parser lo leggesse nel modo che stai suggerendo, saremmo in grado di scrivere int* a, b;e ottenere un paio di puntatori. Il punto che sto sottolineando è che il *legame si lega alla variabile e viene analizzato con essa, non con il tipo per formare il "tipo base" della dichiarazione. Questo è anche uno dei motivi per cui sono stati introdotti i typedef per consentire di typedef int *iptr; iptr a, b;creare un paio di puntatori. Usando un typedef puoi legare *a int.
dgnuff,

1
... Certamente, il compilatore "combina" il tipo di base dal Declaration-Specifiercon le "decorazioni" nel Declaratorper arrivare al tipo finale per ogni variabile. Tuttavia non "sposta" le decorazioni allo specificatore della Dichiarazione, altrimenti int a[10], b;produrrebbe risultati completamente ridicoli, analizza int *a, b[10];come int *a , b[10] ;. Non c'è altro modo per descriverlo che abbia un senso.
Dgnuff,

1
Sì, bene, la parte importante qui non è proprio la sintassi o l'ordine dell'analisi del compilatore, ma che il tipo di int*aè alla fine letto dal compilatore come: " ahas type int*". Questo è ciò che intendo con il mio commento originale.
Lundin,

3

Perché ha più senso quando hai dichiarazioni come:

int *a, *b;

5
Questo è letteralmente un esempio di chiedere la domanda. No, non ha più senso in questo modo. "int * a, b" potrebbe anche rendere entrambi i puntatori.
MichaelGG,

1
@MichaelGG Hai la coda che scuote il cane lì. Sicuramente K&R avrebbe potuto specificare che int* a, b;ha bpuntato a int. Ma non lo fecero. E con una buona ragione. In base al sistema proposto, qual è il tipo di bnella seguente dichiarazione int* a[10], b;:?
dgnuff

2

Per dichiarare più puntatori in una riga, preferisco int* a, * b;che dichiari più intuitivamente "a" come puntatore a un numero intero e non mescoli stili quando allo stesso modo si dichiara "b". Come qualcuno ha detto, non dichiarerei comunque due tipi diversi nella stessa affermazione.


2

Le persone che preferiscono int* x;stanno cercando di forzare il loro codice in un mondo immaginario in cui il tipo è a sinistra e l'identificatore (nome) è a destra.

Dico "immaginario" perché:

In C e C ++, nel caso generale, l'identificatore dichiarato è circondato dalle informazioni sul tipo.

Può sembrare folle, ma sai che è vero. Ecco alcuni esempi:

  • int main(int argc, char *argv[])significa " mainè una funzione che accetta un inte un array di puntatori chare restituisce un int". In altre parole, la maggior parte delle informazioni sul tipo si trova sulla destra. Alcune persone pensano che le dichiarazioni di funzioni non contino perché sono in qualche modo "speciali". OK, proviamo una variabile.

  • void (*fn)(int)significa fnè un puntatore a una funzione che accetta un inte non restituisce nulla.

  • int a[10]dichiara 'a' come un array di 10 ints.

  • pixel bitmap[height][width].

  • Chiaramente, ho scelto esempi ciliegia che hanno molte informazioni sul tipo sulla destra per fare il mio punto. Ci sono molte dichiarazioni in cui la maggior parte - se non tutte - del tipo è sulla sinistra, come struct { int x; int y; } center.

La sintassi di questa dichiarazione è nata dal desiderio di K&R di fare in modo che le dichiarazioni riflettano l'utilizzo. Leggere dichiarazioni semplici è intuitivo e leggere quelle più complesse può essere padroneggiato imparando la regola destra-sinistra-destra (a volte si chiama la regola a spirale o solo la regola di destra-sinistra).

C è abbastanza semplice che molti programmatori C adottano questo stile e scrivono semplici dichiarazioni come int *p.

In C ++, la sintassi è diventata un po 'più complessa (con classi, riferimenti, modelli, classi enum) e, come reazione a quella complessità, vedrai più sforzi nel separare il tipo dall'identificatore in molte dichiarazioni. In altre parole, potresti vedere più int* pdichiarazioni di stile se dai un'occhiata a una grande porzione di codice C ++.

In entrambe le lingue, puoi sempre avere il tipo sul lato sinistro delle dichiarazioni delle variabili non (1) non dichiarando mai più variabili nella stessa istruzione e (2) facendo uso delle typedefdichiarazioni s (o alias, che, ironicamente, mettono l'alias identificatori a sinistra dei tipi). Per esempio:

typedef int array_of_10_ints[10];
array_of_10_ints a;

Piccola domanda, perché non è possibile annullare (* fn) (int) significa "fn è una funzione che accetta un int e restituisce (vuoto *)?
Soham Dongargaonkar,

1
@ a3y3: poiché le parentesi in (*fn)mantengono associato il puntatore fnanziché il tipo restituito.
Adrian McCarthy,

Fatto. Penso che sia abbastanza importante da essere aggiunto nella tua risposta, suggerirò presto una modifica.
Soham Dongargaonkar,

1
Penso che ingombra la risposta senza aggiungere molto valore. Questa è semplicemente la sintassi della lingua. Molte persone chiedono perché la sintassi sia così probabilmente probabilmente già conoscono le regole. Chiunque sia confuso su questo punto può vedere il chiarimento in questi commenti.
Adrian McCarthy,

1

Ho già risposto a una domanda simile in CP e, poiché nessuno lo ha menzionato, anche qui devo sottolineare che C è un linguaggio di formato libero , qualunque stile tu scelga sia ok mentre il parser può distinguere ogni token. Questa peculiarità di C portare ad un tipo molto particolare di concorso chiamato contest offuscamento C .


1

Molti degli argomenti di questo argomento sono semplicemente soggettivi e l'argomento su "la stella si lega al nome della variabile" è ingenuo. Ecco alcuni argomenti che non sono solo opinioni:


Qualificatori del tipo di puntatore dimenticati

Formalmente, la "stella" non appartiene né al tipo né al nome della variabile, fa parte del suo elemento grammaticale chiamato puntatore . La sintassi C formale (ISO 9899: 2018) è:

(6.7) dichiarazione:
specificatori di dichiarazione init-dichiarator-list opt ;

Dove specificatori di dichiarazione contiene il tipo (e l'archiviazione) e l' elenco init-dichiarator contiene il puntatore e il nome della variabile. Che vediamo se analizziamo ulteriormente questa sintassi dell'elenco dei dichiaratori:

(6.7.6) dichiaratore:
puntatore opt -dichiaratore diretto
...
(6.7.6) puntatore:
* tipo-qualificatore-lista opt
* tipo-qualificatore-lista puntatore opt

Laddove un dichiarante è l'intera dichiarazione, un dichiaratore diretto è l'identificatore (nome della variabile) e un puntatore è la stella seguita da un elenco di qualificatori di tipo facoltativo che appartiene al puntatore stesso.

Ciò che rende incoerenti i vari argomenti di stile su "la stella appartiene alla variabile" è che si sono dimenticati di questi qualificatori di tipo puntatore. int* const x, int *const xO int*const x?

Considera int *const a, b;, quali sono i tipi di ae b? Non è così ovvio che "la stella appartiene alla variabile". Piuttosto, si inizierebbe a riflettere su doveconst appartiene.

Puoi sicuramente sostenere che la stella appartiene al qualificatore del tipo di puntatore, ma non molto oltre.

L'elenco dei qualificatori di tipo per il puntatore può causare problemi a chi utilizza lo int *astile. Coloro che usano i puntatori all'interno di un typedef(cosa che non dovremmo, pessima pratica!) E pensano che "la stella appartiene al nome della variabile" tendono a scrivere questo bug molto sottile:

    /*** bad code, don't do this ***/
    typedef int *bad_idea_t; 
    ...
    void func (const bad_idea_t *foo);

Questo si compila in modo pulito. Ora potresti pensare che il codice sia stato reso corretto. Non così! Questo codice è accidentalmente una correttezza const falsa.

Il tipo di fooè in realtà int*const*- il puntatore più esterno è stato reso di sola lettura, non i dati puntati. Quindi all'interno di questa funzione possiamo fare**foo = n; e cambierà il valore della variabile nel chiamante.

Questo perché nell'espressione const bad_idea_t *foo, qui *non appartiene al nome della variabile! Nello pseudo codice, questa dichiarazione di parametro deve essere letta come const (bad_idea_t *) fooe non come (const bad_idea_t) *foo. La stella appartiene al tipo di puntatore nascosto in questo caso: il tipo è un puntatore e un puntatore qualificato const è scritto come *const.

Ma poi la radice del problema nell'esempio sopra è la pratica di nascondere i puntatori dietro a typedefe non lo *stile.


Per quanto riguarda la dichiarazione di più variabili su una sola riga

La dichiarazione di più variabili su una sola riga è ampiamente riconosciuta come cattiva pratica 1) . CERT-C lo riassume bene come:

DCL04-C. Non dichiarare più di una variabile per dichiarazione

Basta leggere l'inglese, quindi il buon senso concorda sul fatto che una dichiarazione dovrebbe essere una dichiarazione.

E non importa se le variabili sono puntatori o meno. Dichiarare ogni variabile su una sola riga rende il codice più chiaro in quasi tutti i casi.

Quindi l'argomento sul confondersi del programmatore int* a, bè negativo. La radice del problema è l'uso di più dichiaratori, non il posizionamento di *. Indipendentemente dallo stile, dovresti invece scrivere questo:

    int* a; // or int *a
    int b;

Un altro argomento valido ma soggettivo sarebbe quello dato int* ail tipo di aè senza dubbioint* e quindi la stella appartiene al qualificatore di tipo.

Ma sostanzialmente la mia conclusione è che molti degli argomenti pubblicati qui sono solo soggettivi e ingenui. Non puoi davvero fare un valido argomento per entrambi gli stili - è davvero una questione di preferenze personali soggettive.


1) CERT-C DCL04-C .


Piuttosto divertente, se leggi quell'articolo che ho collegato, c'è una breve sezione sull'argomento di typedef int *bad_idea_t; void func(const bad_idea_t bar); Come il grande profeta Dan Saks insegna "Se posizioni sempre il constpiù lontano possibile, senza cambiare il significato semantico" questo cessa completamente essere un problema. Inoltre, rende le tue constdichiarazioni più coerenti da leggere. "Tutto a destra della parola const è ciò che è const, tutto a sinistra è il suo tipo." Ciò si applicherà a tutti i contro in una dichiarazione. Provalo conint const * * const x;
dgnuff il

-1

Tener conto di

int *x = new int();

Questo non si traduce in

int *x;
*x = new int();

In realtà si traduce in

int *x;
x = new int();

Questo rende la int *xnotazione in qualche modo incoerente.


newè un operatore C ++. La sua domanda è taggata "C" e non "C ++".
Sapphire_Brick il
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.