Por que a sintaxe C para matrizes, ponteiros e funções foi projetada dessa maneira?

14

Depois de ter visto (e perguntado!) tantas perguntas semelhantes a

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

e mesmo vendo que eles fizeram um programa para ajudar as pessoas a entender a sintaxe C, não posso deixar de me perguntar:

Por que a sintaxe de C foi projetada dessa maneira?

Por exemplo, se eu estivesse projetando ponteiros, eu traduziria "um ponteiro para uma matriz de 10 elementos de ponteiros" em

int*[10]* p;

e não

int* (*p)[10];

que eu sinto que a maioria das pessoas concordaria é muito menos simples.

Então, estou pensando, por que a sintaxe não intuitiva? Houve algum problema específico que a sintaxe resolve (talvez uma ambigüidade?) De que não tenho conhecimento?

    
por Mehrdad 31.10.2011 / 05:54
fonte

5 respostas

14

Meu entendimento da história é que é baseado em dois pontos principais ...

Em primeiro lugar, os autores de linguagem preferiram tornar a sintaxe centrada na variável em vez de centrada no tipo. Ou seja, eles queriam que um programador olhasse para a declaração e pensasse "se eu escrever a expressão *func(arg) , isso resultará em um int ; se eu escrever *arg[N] , eu terei um float" em vez de " func deve ser um ponteiro para uma função tomando isso e retornando que ".

A entrada C na Wikipedia afirma que:

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

... citando a p122 do K & R2, que, infelizmente, não tenho que entregar para encontrar a citação extensa para você.

Em segundo lugar, é realmente muito difícil encontrar uma sintaxe para declaração consistente quando você está lidando com níveis arbitrários de indireção. Seu exemplo pode funcionar bem para expressar o tipo que você imaginou fora do bastão, mas ele é escalonado para uma função levando um ponteiro para uma matriz desses tipos e retornando alguma outra confusão horrível? (Talvez sim, mas você conferiu? Você pode provar?).

Lembre-se de que parte do sucesso de C se deve ao fato de compiladores terem sido escritos para muitas plataformas diferentes e, portanto, pode ter sido melhor ignorar algum grau de legibilidade para facilitar a compilação de compiladores.

Dito isto, não sou especialista em gramática de línguas ou escrita de compiladores. Mas eu sei o suficiente para saber que há muito a saber;)

    
por 31.10.2011 / 09:18
fonte
12

Muitas esquisitices da linguagem C podem ser explicadas pela maneira como os computadores funcionavam quando eram projetados. Havia quantidades muito limitadas de memória de armazenamento, por isso era muito importante minimizar o tamanho dos arquivos de código-fonte . A prática de programação nos anos 70 e 80 era garantir que o código-fonte contivesse o mínimo possível de caracteres e, de preferência, sem comentários excessivos sobre o código-fonte.

Isto é obviamente ridículo hoje, com espaço de armazenamento praticamente ilimitado em discos rígidos. Mas é parte da razão pela qual C tem uma sintaxe tão estranha em geral.

Em relação especificamente aos ponteiros de array, seu segundo exemplo deve ser int (*p)[10]; (a sintaxe é muito confusa). Eu talvez lesse isso como "apontador int para array de dez" ... o que faz sentido de alguma forma. Se não fosse o parêntese, o compilador o interpretaria como uma matriz de dez ponteiros, o que daria à declaração um significado totalmente diferente.

Como os ponteiros de array e os ponteiros de função têm uma sintaxe bastante obscura em C, a coisa mais sensata a se fazer é tipificar a estranheza. Talvez assim:

Exemplo obscuro:

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);
}

Exemplo equivalente não obscuro:

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);
}

As coisas podem ficar ainda mais obscuras se você estiver lidando com matrizes de ponteiros de função. Ou o mais obscuro de todos: funções retornando ponteiros de função (levemente úteis). Se você não usa typedefs para essas coisas, você enlouquece rapidamente.

    
por 31.10.2011 / 08:59
fonte
7

É bem simples: int *p significa que *p é um int; int a[5] significa que a[i] é um int.

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

Significa que *f é uma função, *a é uma matriz de cinco inteiros, portanto f é uma função que usa um ponteiro para uma matriz de cinco inteiros e retorna int. No entanto, em C, não é útil passar um ponteiro para um array.

Declarações C muito raramente tornam isso complicado.

Além disso, você pode esclarecer usando typedefs:

typedef int vec5[5];
int (*f)(vec5 *a);
    
por 31.10.2011 / 06:06
fonte
3

Acho que você deve considerar * [] como operadores que estão anexados a uma variável. * é escrito antes de uma variável, [] depois.

Vamos ler a expressão de tipo

int* (*p)[10];

O elemento mais interno é p, uma variável, portanto

p

significa: p é uma variável.

Antes da variável existe um *, o operador * é sempre colocado antes da expressão a que se refere, portanto,

(*p)

significa: a variável p é um ponteiro. Sem o () o operador [] à direita teria maior precedência, ou seja,

**p[]

seria analisado como

*(*(p[]))

O próximo passo é []: como não há mais (), [] tem maior precedência que o exterior *, portanto

(*p)[]

significa: (variável p é um ponteiro) para um array. Então nós temos o segundo *:

* (*p)[]

significa: ((variável p é um ponteiro) para um array) de ponteiros

Finalmente, você tem o operador int (um nome de tipo), que tem a menor precedência:

int* (*p)[]

significa: (((variável p é um ponteiro) para uma matriz) de ponteiros) para inteiro.

Assim, todo o sistema é baseado em expressões de tipo com operadores e cada operador tem suas próprias regras de precedência. Isso permite definir tipos muito complexos.

    
por 31.10.2011 / 07:31
fonte
0

Não é tão difícil quando você começa a pensar e C nunca foi uma linguagem muito fácil. E int*[10]* p realmente não é mais fácil que int* (*p)[10] E que tipo de k estaria em int*[10]* p, k;

    
por 31.10.2011 / 09:59
fonte

Tags