¿Por qué la sintaxis de C para matrices, punteros y funciones se diseñó de esta manera?

14

Después de haber visto (y preguntado) tantas preguntas similares a

  

¿Qué significa int (*f)(int (*a)[5]) en C?

e incluso viendo que habían hecho un programa para ayudar a las personas a entender la sintaxis de C, no puedo dejar de preguntarme:

¿Por qué la sintaxis de C se diseñó de esta manera?

Por ejemplo, si estuviera diseñando punteros, traduciría "un puntero a una matriz de 10 elementos de punteros" en

int*[10]* p;

y not

int* (*p)[10];

Lo que creo que la mayoría de la gente estaría de acuerdo es mucho menos sencillo.

Así que me pregunto, ¿por qué, uh, la sintaxis no intuitiva? ¿Hubo un problema específico que la sintaxis resuelve (tal vez una ambigüedad?) Que no conozco?

    
pregunta Mehrdad 31.10.2011 - 05:54
fuente

5 respuestas

14

Mi comprensión de la historia es que se basa en dos puntos principales ...

En primer lugar, los autores de idiomas preferían que la sintaxis fuera centrada en variables en lugar de centrada en tipos. Es decir, querían que un programador mirara la declaración y pensara "si escribo la expresión *func(arg) , eso dará como resultado un int ; si escribo *arg[N] tendré un flotador" en lugar de " func debe ser un puntero a una función que toma this y devuelve that ".

La entrada C en Wikipedia afirma que:

  

La idea de Ritchie fue declarar identificadores en contextos que se parecen a su uso: "declaración refleja uso".

... citando la p122 de K & R2 que, por desgracia, no tengo que darte la mano para encontrar la cotización extendida para ti.

En segundo lugar, es realmente muy difícil encontrar una sintaxis para la declaración que sea consistente cuando se trata de niveles arbitrarios de indirección. Su ejemplo podría funcionar bien para expresar el tipo que pensó fuera del bate allí, pero ¿se puede escalar a una función que toma un puntero a una matriz de esos tipos y devuelve algún otro desorden horrible? (Tal vez sí, pero ¿comprobaste? ¿Puedes probarlo? ).

Recuerde, parte del éxito de C se debe al hecho de que los compiladores se escribieron para muchas plataformas diferentes, por lo que podría haber sido mejor ignorar un cierto grado de legibilidad para hacer que los compiladores sean más fáciles de escribir.

Habiendo dicho eso, no soy un experto en gramática lingüística ni en compilación. Pero sé lo suficiente para saber que hay mucho que saber;)

    
respondido por el detly 31.10.2011 - 09:18
fuente
12

Muchas de las rarezas del lenguaje C se pueden explicar por la forma en que funcionaban las computadoras cuando se diseñaron. Había una cantidad muy limitada de memoria de almacenamiento, por lo que era muy importante minimizar el tamaño de los archivos de código fuente . La práctica de programación en los años 70 y 80 consistió en asegurarse de que el código fuente contenga la menor cantidad de caracteres posible y, preferiblemente, que no haya comentarios excesivos en el código fuente.

Esto es, por supuesto, ridículo hoy en día, con bastante espacio de almacenamiento ilimitado en los discos duros. Pero es parte de la razón por la que C tiene una sintaxis tan extraña en general.

Respecto a los punteros de matriz específicamente, su segundo ejemplo debería ser int (*p)[10]; (sí, la sintaxis es muy confusa). Tal vez lo lea como "int puntero a matriz de diez" ... lo que tiene sentido de alguna manera. Si no fuera por el paréntesis, el compilador lo interpretaría como una matriz de diez punteros, lo que daría a la declaración un significado completamente diferente.

Dado que los punteros de matriz y los punteros de función tienen una sintaxis bastante oscura en C, lo más sensato es descifrar la rareza. Quizás así:

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

Ejemplo no oscuro, 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);
}

Las cosas pueden volverse aún más oscuras si se trata de matrices de punteros de función. O el más oscuro de todos ellos: funciones que devuelven punteros a funciones (algo útiles). Si no usa typedefs para tales cosas, rápidamente se volverá loco.

    
respondido por el user29079 31.10.2011 - 08:59
fuente
7

Es bastante simple: int *p significa que *p es un int; int a[5] significa que a[i] es un int.

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

Significa que *f es una función, *a es una matriz de cinco enteros, por lo que f es una función que lleva un puntero a una matriz de cinco enteros y devuelve int. Sin embargo, en C no es útil pasar un puntero a una matriz.

Las declaraciones de C muy rara vez se complican así.

Además, puedes aclarar usando typedefs:

typedef int vec5[5];
int (*f)(vec5 *a);
    
respondido por el kevin cline 31.10.2011 - 06:06
fuente
3

Creo que hay que considerar * [] como operadores que están vinculados a una variable. * está escrito antes de una variable, [] después.

Leamos la expresión de tipo

int* (*p)[10];

El elemento más interno es p, una variable, por lo tanto

p

significa: p es una variable.

Antes de la variable hay un *, el operador * siempre se coloca antes de la expresión a la que hace referencia, por lo tanto,

(*p)

significa: la variable p es un puntero. Sin el () el operador [] a la derecha tendría mayor prioridad, es decir,

**p[]

se analizaría como

*(*(p[]))

El siguiente paso es []: ya que no hay más (), [] tiene mayor prioridad que el exterior *, por lo tanto

(*p)[]

significa: (la variable p es un puntero) a una matriz. Luego tenemos el segundo *:

* (*p)[]

significa: ((la variable p es un puntero) a una matriz) de punteros

Finalmente tiene el operador int (un nombre de tipo), que tiene la prioridad más baja:

int* (*p)[]

significa: ((((la variable p es un puntero) a una matriz) de punteros) a entero.

Por lo tanto, todo el sistema se basa en expresiones de tipo con operadores, y cada operador tiene sus propias reglas de precedencia. Esto permite definir tipos muy complejos.

    
respondido por el Giorgio 31.10.2011 - 07:31
fuente
0

No es tan difícil cuando empiezas a pensar y C nunca fue un lenguaje muy fácil. Y int*[10]* p realmente no es más fácil que int* (*p)[10] ¿Y qué tipo de k estaría en int*[10]* p, k;

    
respondido por el Dainius 31.10.2011 - 09:59
fuente

Lea otras preguntas en las etiquetas