Perché la sintassi C per matrici, puntatori e funzioni è stata progettata in questo modo?

14

Dopo aver visto (e chiesto!) tante domande simili a

What does int (*f)(int (*a)[5]) mean in C?

e anche vedendo che avevano fatto un programma per aiutare le persone a capire la sintassi C, non posso fare a meno di chiedermi:

Perché la sintassi di C è stata progettata in questo modo?

Ad esempio, se dovessi progettare i puntatori, tradurrei "un puntatore a un array di 10 puntatori" in

int*[10]* p;

e non

int* (*p)[10];

che ritengo che la maggior parte delle persone sarebbe d'accordo è molto meno semplice.

Quindi mi chiedo, perché la sintassi, non intuitiva? C'è stato un problema specifico risolto dalla sintassi (forse un'ambiguità?) Di cui non sono a conoscenza?

    
posta Mehrdad 31.10.2011 - 05:54
fonte

5 risposte

14

La mia comprensione della storia di esso è che si basa su due punti principali ...

In primo luogo, gli autori del linguaggio preferivano rendere la sintassi incentrata sulla variabile piuttosto che sul tipo-centrico. Cioè, volevano che un programmatore guardasse la dichiarazione e pensasse che "se scrivo l'espressione *func(arg) , ciò produrrà un int ; se scrivo *arg[N] avrò un float" piuttosto che " func deve essere un puntatore a una funzione che prende questo e restituisce tale ".

La voce C su Wikipedia afferma che:

Ritchie's idea was to declare identifiers in contexts resembling their use: "declaration reflects use".

... citando p122 di K & R2 che, ahimè, non ho a disposizione per trovare la citazione estesa per te.

In secondo luogo, in realtà è davvero molto difficile trovare una sintassi per la dichiarazione che sia coerente quando si ha a che fare con livelli arbitrari di indirezione. Il tuo esempio potrebbe funzionare bene per esprimere il tipo che hai ideato fuori dal blocco, ma lo ridimensiona in base a una funzione che punta un puntatore a un array di questi tipi e restituisce un altro pasticcio odioso? (Forse lo fa, ma hai controllato? Puoi provarlo? ).

Ricorda che parte del successo di C è dovuto al fatto che i compilatori sono stati scritti per molte piattaforme diverse, quindi sarebbe stato meglio ignorare un certo grado di leggibilità per rendere più facile la scrittura dei compilatori.

Detto questo, non sono un esperto in grammatica o scrittura di compilatori. Ma so abbastanza per sapere che c'è molto da sapere;)

    
risposta data 31.10.2011 - 09:18
fonte
12

Molte delle stranezze del linguaggio C possono essere spiegate dal modo in cui i computer hanno funzionato quando è stato progettato. C'erano quantità molto limitate di memoria di archiviazione, quindi era molto importante minimizzare la dimensione dei file di codice sorgente stessi. La pratica della programmazione negli anni '70 e '80 era di assicurarsi che il codice sorgente contenesse il minor numero possibile di caratteri, e preferibilmente nessun commento eccessivo sul codice sorgente.

Questo è ovviamente ridicolo oggi, con spazio di archiviazione praticamente illimitato sui dischi rigidi. Ma è parte del motivo per cui C ha una sintassi così bizzarra in generale.

Per quanto riguarda specificamente i puntatori di array, il tuo secondo esempio dovrebbe essere int (*p)[10]; (sì la sintassi è molto confusa). Potrei leggerlo come "int pointer to array of ten" ... che ha un senso in qualche modo. Se non fosse per la parentesi, il compilatore lo interpreterebbe come una matrice di dieci puntatori, il che darebbe alla dichiarazione un significato completamente diverso.

Poiché i puntatori di array e i puntatori di funzione hanno entrambi una sintassi piuttosto oscura in C, la cosa più sensata da fare è tipizzare la stranezza. Forse in questo modo:

Esempio oscuro:

int func (int (*arr_ptr)[10])
{
  return 0;
}

int main()
{
  int array[10];
  int (*arr_ptr)[10]  = &array;
  int (*func_ptr)(int(*)[10]) = &func;

  func_ptr(arr_ptr);
}

Esempio non oscuro e equivalente:

typedef int array_t[10];
typedef int (*funcptr_t)(array_t*);


int func (array_t* arr_ptr)
{
  return 0;
}

int main()
{
  int        array[10];
  array_t*   arr_ptr  = &array; /* non-obscure array pointer */
  funcptr_t  func_ptr = &func;  /* non-obscure function pointer */

  func_ptr(arr_ptr);
}

Le cose possono diventare ancora più oscure se si hanno a che fare con matrici di puntatori di funzioni. O il più oscuro di tutti: le funzioni che restituiscono i puntatori alle funzioni (leggermente utili). Se non usi typedef per tali cose, diventerai presto pazzo.

    
risposta data 31.10.2011 - 08:59
fonte
7

È piuttosto semplice: int *p significa che *p è un int; int a[5] significa che a[i] è un int.

int (*f)(int (*a)[5])

Significa che *f è una funzione, *a è una matrice di cinque numeri interi, quindi f è una funzione che assume un puntatore a una matrice di cinque numeri interi e che restituisce int. Tuttavia, in C non è utile passare un puntatore a un array.

Le dichiarazioni C raramente si complicano così.

Inoltre, puoi chiarire usando typedef:

typedef int vec5[5];
int (*f)(vec5 *a);
    
risposta data 31.10.2011 - 06:06
fonte
3

Penso che devi considerare * [] come operatori collegati a una variabile. * è scritto prima di una variabile, [] dopo.

Leggiamo l'espressione di tipo

int* (*p)[10];

L'elemento più interno è p, una variabile, quindi

p

significa: p è una variabile.

Prima della variabile c'è un *, l'operatore * è sempre messo prima dell'espressione a cui fa riferimento, quindi,

(*p)

significa: la variabile p è un puntatore. Senza () l'operatore [] a destra avrebbe precedenza più alta, cioè

**p[]

sarebbe analizzato come

*(*(p[]))

Il prossimo passo è []: poiché non c'è più (), [] ha precedenza più alta di quella esterna *, quindi

(*p)[]

significa: (variabile p è un puntatore) in una matrice. Quindi abbiamo il secondo *:

* (*p)[]

significa: ((p variabile è un puntatore) in un array) di puntatori

Finalmente hai l'operatore int (un nome di tipo), che ha la precedenza più bassa:

int* (*p)[]

significa: (((p variabile è un puntatore) in un array) di puntatori) in intero.

Quindi l'intero sistema si basa su espressioni di tipo con operatori e ogni operatore ha le sue regole di precedenza. Questo permette di definire tipi molto complessi.

    
risposta data 31.10.2011 - 07:31
fonte
0

Non è così difficile quando inizi a pensare e C non è mai stato un linguaggio facile. E int*[10]* p in realtà non è più semplice di int* (*p)[10] E quale tipo di k sarebbe in int*[10]* p, k;

    
risposta data 31.10.2011 - 09:59
fonte

Leggi altre domande sui tag