In C, è * un operatore, o parte di un tipo in una dichiarazione?

9

In C, * è chiamato l'operatore di riferimento indiretto o l'operatore di dereferenziazione. Capisco come funziona quando è usato in una dichiarazione. Ha senso scrivere *p o * p , considerando che si tratta di un operatore unario.

Tuttavia, a volte in una dichiarazione, viene utilizzato un * .

void move(int *units) { ... }

o

int *p = &x;

Mi sembra strano che qui venga usato un operatore. Non puoi fare cose come

void move(int units++) { ... }

dove ++ è anche un operatore unario. È questo * anche l'operatore di riferimento indiretto, o questo * ha un significato diverso, dove dice qualcosa sul tipo di puntatore? (Se questo è il caso, sto considerando di usare per esempio int* units nelle dichiarazioni e int x = *p; altrimenti per chiarezza.)

In questo rispondi si dice che

The C standard only defines two meanings to the * operator:

  • indirection operator
  • multiplication operator

Ho visto anche persone che affermano che il * è in realtà parte del tipo. Questo mi confonde.

    
posta Torm 23.08.2016 - 01:06
fonte

6 risposte

15

Le parti più piccole del linguaggio C sono token lessicali , come le parole chiave (ad esempio int , if , break ), identificatori (ad esempio move , units ) e altri.

* è un elemento lessicale chiamato punctuator (come ad esempio {}()+ ). Un punteggiatore può avere significati diversi a seconda di come viene utilizzato:

A punctuator is a symbol that has independent syntactic and semantic significance. Depending on context, it may specify an operation to be performed (...) in which case it is known as an operator

Nello standard C11, capitolo 6.5 sulle espressioni, * è definito come operatore unario (sezione 6.5.3.2, per riferimento indiretto) e come operatore moltiplicativo (sezione 6.5.5), esattamente come descritto. Ma * è interpretato solo come operatore in base alle regole grammaticali che rendono valide le espressioni.

Ma c'è anche un capitolo 6.2 sui concetti, che spiega che i puntatori di un tipo sono tipi derivati. La sezione 6.7.6.1 sui dichiarazioni dei puntatori è più precisa:

if, in the declaration ‘‘T D1’’, D1 has the form
* type-qualifier-list-opt D
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list T’’, then the type specified for ident is ‘‘derived-declarator-type-list type-qualifier-list pointer to T’’.

Questo fa effettivamente * parte di un tipo derivato ( * significa "puntatore", ma ha sempre bisogno di un altro tipo per dire a che tipo di cosa punta a qualcosa).

Nota: non c'è alcuna contraddizione in questo. Quando parli inglese fai un uso così differenziato dell'elemento lessicale del linguaggio: "un handle " designa qualcosa e "per gestire " significa fare qualcosa, due usi grammaticali completamente diversi di lo stesso elemento lessicale.

    
risposta data 23.08.2016 - 01:16
fonte
7

In C, l'indirezione in una dichiarazione è meglio leggere come parte della variabile rispetto a parte del tipo. Se ho:

int *a;

questa frase potrebbe essere interpretata come segue: dichiarare una variabile, chiamata a , che quando dereferenziato è di tipo int .

Questo è importante quando vengono dichiarate più variabili contemporaneamente:

int *a, b;   (1)
int* a, b;   (2)

Entrambe le dichiarazioni sono equivalenti, e in entrambi a e b non sono gli stessi tipi - a è un puntatore a un int e b è un int. Ma dichiaraction (2) sembra come se il "tipo di puntatore" fosse parte del tipo quando è effettivamente la dichiarazione di *a .

    
risposta data 23.08.2016 - 01:27
fonte
4

Per aggiungere altre risposte corrette:

È solo un'espressione di dichiarazione che riflette le potenziali espressioni di utilizzo. IIRC, penso che Kernigan o Ritchie l'abbiano chiamato un esperimento! Ecco alcuni testi di supporto marginale:

Back in the 70's, when Kernighan and Ritchie were writing The C Programming Language, they were pretty honest about how declarations can quickly become unreadable by the untrained eye:

C is sometimes castigated for the syntax of its declarations, particularly ones that involve pointers to functions. The syntax is an attempt to make the declaration and the use agree; it works well for the simple cases, but it can be confusing for the harder ones, because declarations cannot be read left to right, and because parentheses are over-used. [...]

(La mia enfasi.)

link

Quindi, sì, hai ragione, questi sono in effetti gli stessi operatori applicati alle espressioni di dichiarazione, anche se come hai osservato solo alcuni degli operatori hanno senso (ad esempio ++ non lo fa).

Tale stella fa effettivamente parte del tipo di variabile individuale dichiarata; tuttavia, come hanno notato altri poster, non è (più precisamente, non può) essere trasferito ad altre variabili dichiarate contemporaneamente (cioè nella stessa dichiarazione) - ogni variabile inizia con lo stesso tipo di base di l'obiettivo (eventuale), quindi ottiene il proprio (possibilità di applicare o meno) operatori di puntatori, array e funzioni per completare il suo tipo.

Questo è il motivo per cui i programmatori esperti tendono a preferire (1) int *p; rispetto a int* p; e (2) dichiarando solo una singola variabile per dichiarazione, anche se il linguaggio consente di più.

Per aggiungere che int (*p); è una dichiarazione legale, questi parenti si limitano a raggruppare e in questo caso semplice e non fanno nulla di diverso da int *p . È come dire che un'espressione (non dichiarativa) (i) >= (0) è uguale a i >= 0 , dato che puoi sempre aggiungere legalmente () .

Ne parlo come un altro esempio del perché i programmatori preferiscono un modulo rispetto all'altro, quindi considera int (*p); rispetto a int(* p); . Forse puoi vedere come la parentesi, la stella, ecc. Si applicano alla variabile non al tipo di base (cioè aggiungendo tutte le parentesi consentite).

    
risposta data 23.08.2016 - 04:10
fonte
1

In questa dichiarazione di funzione:

void move(int *units);

il * è parte del tipo, ovvero int* . Non è un operatore. Ecco perché molte persone lo scriverebbero come:

void move(int* units);
    
risposta data 23.08.2016 - 09:00
fonte
1

Solo gli operatori * , [] e () hanno un significato nelle dichiarazioni (C ++ aggiunge & , ma non entreremo in questo qui).

Nella dichiarazione

int *p;       

il int -ness di p è specificato dallo specificatore del tipo int , mentre il puntatore-ness di p è specificato dal dichiaratore *p .

Il tipo di p è "puntatore a int "; questo tipo è completamente specificato dalla combinazione dello specificatore di tipi int più il dichiaratore *p .

In una dichiarazione, il dichiaratore introduce il nome della cosa dichiarata ( p ) insieme a informazioni di tipo aggiuntive non specificate dallo specificatore di tipo ("puntatore a"):

T v;     // v is a single object of type T, for any type T
T *p;    // p is a pointer to T, for any type T
T a[N];  // a is an N-element array of T, for any type T
T f();   // f is a function returning T, for any type T

Questo è importante - il puntatore, l'array-ness e la funzionalità sono specificati come parte del dichiaratore, non lo specificatore di tipo 1 . Se scrivi

int* a, b, c;

sarà analizzato come

int (*a), b, c;

quindi solo a sarà dichiarato come puntatore a int ; b e c sono dichiarati come% normaleint s.

Gli operatori * , [] e () possono essere combinati per creare tipi arbitrariamente complessi:

T *a[N];      // a is an N-element array of pointers to T
T (*a)[N];    // a is a pointer to an N-element array of T
T *(*f[N])(); // f is an N-element array of pointers to functions 
              // returning pointer to T

T *(*(*(*f)[N])())[M]  // f is a pointer to an N-element array of pointers
                       // to functions returning pointers to M-element
                       // arrays of pointers to T

Tieni presente che * , [] e () rispettano le stesse regole di precedenza nelle dichiarazioni che fanno nelle espressioni. *a[N] è analizzato come *(a[N]) in entrambe le dichiarazioni e le espressioni.

La cosa veramente importante da comprendere in questo è che la forma di una dichiarazione corrisponde alla forma dell'espressione nel codice. Tornando al nostro esempio originale, abbiamo un puntatore a un intero chiamato p . Se vogliamo recuperare il valore intero, usiamo l'operatore * per dereferenziare p , in questo modo:

x = *p;

Il tipo di espressione *p è int , che segue dalla dichiarazione

int *p;

Allo stesso modo, se abbiamo un array di puntatori a double e vogliamo recuperare un valore specifico, indichiamo nell'array e dereferenziamo il risultato:

y = *ap[i];

Ancora, il tipo di espressione *ap[i] è double , che segue dalla dichiarazione

double *ap[N];

Quindi, perché ++ non ha un ruolo in una dichiarazione come * , [] o () ? O qualsiasi altro operatore come + o -> o && ?

Bene, in pratica, perché la definizione della lingua lo dice. Mette da parte solo * , [] e () per giocare qualsiasi ruolo in una dichiarazione, dal momento che devi essere in grado di specificare il puntatore, l'array e i tipi di funzione. Non esiste un tipo "increment-this" separato, quindi non è necessario che ++ faccia parte di una dichiarazione. Non esiste un tipo "bitwise-this", quindi non c'è bisogno di unary & , | , ^ o ~ per far parte di una dichiarazione. Per i tipi che utilizzano l'operatore di selezione membri . , utilizziamo i tag struct e union nella dichiarazione. Per i tipi che utilizzano l'operatore -> , utilizziamo i tag struct e union in combinazione con l'operatore * nel dichiaratore.

  1. Naturalmente, puoi creare nomi typedef per i tipi di puntatore, array e funzione, come
    typedef int *iptr;
    iptr a,b,c; // all three of a, b, and c are pointers to int
    
    ma ancora, è il dichiaratore *iptr che specifica il puntatore del nome typedef.

risposta data 26.08.2016 - 23:46
fonte
1

The C standard only defines two meanings to the * operator:

  • indirection operator
  • multiplication operator

È vero che ci sono due significati per l'operatore * . Non è vero che quelli sono gli unici significati del * token .

In una dichiarazione come

int *p;

il * non è un operatore; fa parte della sintassi di una dichiarazione.

La sintassi delle dichiarazioni di C è intesa a rispecchiare la sintassi delle espressioni, quindi la dichiarazione precedente può essere letta come " *p è di tipo int ", da cui possiamo dedurre che p è di tipo int* . Tuttavia, questa non è una regola ferma, e non è indicata da nessuna parte nello standard. Invece, la sintassi delle dichiarazioni è definita a sé stante, separatamente dalla sintassi delle espressioni. Ecco perché, per esempio, la maggior parte degli operatori che possono apparire nelle espressioni non possono apparire con lo stesso significato nelle dichiarazioni (a meno che non facciano parte di un'espressione che fa parte della dichiarazione, come l'operatore + in int array[2+2]; ).

Un caso in cui il principio "dichiarazione uso specchi" non è abbastanza il lavoro è:

int arr[10];

dove arr[10] sarebbe di tipo int se esistesse .

E infatti c'è ancora un altro uso del token * . Un parametro array può essere dichiarato con [*] per indicare una matrice a lunghezza variabile. (Questo è stato aggiunto in C99.) Il * non è né una moltiplicazione né un dereferenziamento. Sospetto che sia stato ispirato dalla sintassi dei caratteri jolly della shell.

    
risposta data 29.08.2016 - 21:34
fonte

Leggi altre domande sui tag