¿Por qué el nombre de la matriz es un puntero al primer elemento de la matriz?

Siempre es así, quiero decir, ese nombre de matriz siempre es un puntero al primer elemento de la matriz. ¿Por qué es así? ¿Es algo de implementación algo o una función de idioma?

Un nombre de matriz no es en sí mismo un puntero, sino que se descompone en un puntero al primer elemento de la matriz en la mayoría de los contextos. Es así porque el lenguaje lo define de esa manera.

De C11 6.3.2.1 Valores L, matrices y designadores de funciones , párrafo 3:

Excepto cuando es el operando del operador sizeof , el operador _Alignof , o el operador unario, o es un literal de cadena usado para inicializar una matriz, una expresión que tiene tipo “array of type ” se convierte en una expresión con tipo ” puntero para escribir “que apunta al elemento inicial del objeto matriz y no es un valor l.

Puede obtener más información sobre este tema (y mucho sobre el comportamiento sutil involucrado) de la sección Arrays and Pointers de las preguntas frecuentes de comp.lang.c.

Editorial a un lado: el mismo tipo de comportamiento tiene lugar en C ++, aunque el lenguaje lo especifica de manera un poco diferente. Como referencia, de un borrador de C ++ 11 que tengo aquí, 4.2 Conversión de matriz a puntero , párrafo 1:

Un lvalue o rvalue de tipo “array of N T ” o “array of unknown bound of T ” se puede convertir a un valor de tipo “pointer to T “. El resultado es un puntero al primer elemento de la matriz.

La razón histórica de este comportamiento se puede encontrar aquí .

C se derivó de un lenguaje anterior llamado B (go figure). B era un lenguaje sin teclas, y la memoria se trataba como una matriz lineal de “células”, básicamente enteros sin signo.

En B, cuando declaraste una matriz de N elementos, como en

 auto a[10]; 

Se asignaron N celdas para el conjunto y se reservó otra celda para almacenar la dirección del primer elemento, que estaba vinculada a la variable a . Como en C, la indexación de matriz se realizó a través de la aritmética del puntero:

 a[j] == *(a+j) 

Esto funcionó bastante bien hasta que Ritchie comenzó a agregar tipos de estructura a C. El ejemplo que da en el documento es una entrada hipotética del sistema de archivos, que es una identificación de nodo seguida de un nombre:

 struct { int inumber; char name[14]; }; 

Quería que los contenidos del tipo de estructura coincidieran con los datos en el disco; 2 bytes para un entero seguido inmediatamente por 14 bytes para el nombre. No había un buen lugar para esconder el puntero al primer elemento de la matriz.

Entonces él se deshizo de eso. En lugar de dejar de lado el almacenamiento para el puntero, diseñó el lenguaje de modo que el valor del puntero se computaría a partir de la expresión de la matriz misma.

Esto, por cierto, es por qué una expresión de matriz no puede ser el objective de una tarea; en realidad es lo mismo que escribir 3 = 4; – Estarías tratando de asignar un valor a otro valor.

Carl Norum ha dado la respuesta del abogado de idiomas sobre la pregunta (y obtuve mi voto al respecto), aquí viene la respuesta detallada de la implementación:

Para la computadora, cualquier objeto en la memoria es solo un rango de bytes y, en lo que respecta al manejo de la memoria, identificado de manera única por una dirección en el primer byte y un tamaño en bytes. Incluso cuando tiene un int en la memoria, su dirección no es más que la dirección de su primer byte. El tamaño casi siempre está implícito: si pasa un puntero a un int , el comstackdor conocerá su tamaño porque sabe que los bytes en esa dirección deben interpretarse como un int . Lo mismo ocurre con las estructuras: su dirección es la dirección de su primer byte, su tamaño es implícito.

Ahora, los diseñadores del lenguaje podrían haber implementado una semántica similar con las matrices, como lo hicieron con las estructuras, pero no lo hicieron por una buena razón: Copiar era aún más ineficiente que ahora en comparación con simplemente pasar un puntero, las estructuras ya se habían pasado usando punteros la mayor parte del tiempo, y los arreglos suelen ser grandes. Prohibitivamente grande para forzar la semántica del valor en ellos por el lenguaje.

Por lo tanto, las matrices se vieron obligadas a ser objetos de memoria en todo momento al especificar que el nombre de una matriz sería prácticamente equivalente a un puntero. Para no romper la similitud de las matrices con otros objetos de memoria, se volvió a decir que el tamaño estaba implícito (para la implementación del lenguaje, ¡no para el progtwigdor!): El comstackdor podía olvidarse del tamaño de una matriz cuando se pasaba en otro lugar y confíe en el progtwigdor para saber cuántos objetos había dentro de la matriz.

Esto tuvo el beneficio de que los accesos a arreglos son increíblemente simples; decaen a una cuestión de aritmética de puntero, de multiplicar el índice por el tamaño del objeto en la matriz y agregar ese desplazamiento al puntero. Es la razón por la que a[5] es exactamente lo mismo que 5[a] , es una forma abreviada de *(a + 5) .

Otro aspecto relacionado con el rendimiento es que es increíblemente simple crear un subcampo de una matriz: solo se debe calcular la dirección de inicio. No hay nada que nos obligue a copiar los datos en una nueva matriz, solo tenemos que recordar usar el tamaño correcto …

Entonces, sí, tiene razones profundas en términos de simplicidad de implementación y rendimiento para que los nombres de los arreglos decaigan a indicadores de la manera en que lo hacen, y deberíamos estar contentos por ello.