¿Por qué el tipo de objeto de matriz no es modificable?

Aquí se afirma que

El término valor l modificable se usa para enfatizar que el valor l permite que el objeto designado sea cambiado y examinado. Los siguientes tipos de objetos son lvalues, pero no lvalues ​​modificables:

  • Un tipo de matriz
  • Un tipo incompleto
  • Un tipo const-calificado
  • Una estructura o tipo de unión con uno de sus miembros calificado como un tipo const

Debido a que estos lvalues ​​no son modificables, no pueden aparecer en el lado izquierdo de una statement de asignación.

¿Por qué el tipo de objeto de matriz no es modificable? ¿No es correcto escribir

int i = 5, a[10] = {0}; a[i] = 1; 

?
Y también, ¿qué es un tipo incompleto?

Asume la statement

 int a[10]; 

entonces todo lo siguiente es verdadero:

  • el tipo de la expresión a es “matriz de 10 elementos de int “; excepto cuando a es el operando de sizeof o unary & operadores, la expresión se convertirá en una expresión de tipo “puntero a int ” y su valor será la dirección del primer elemento en la matriz;
  • el tipo de la expresión a[i] es int ; se refiere al objeto entero almacenado como el i -ésimo elemento de la matriz;
  • La expresión a no puede ser el objective de una asignación porque C no trata las matrices como otras variables, por lo que no puede escribir algo como a = b o a = malloc(n * sizeof *a) o algo por el estilo.

Notarás que sigo enfatizando la palabra “expresión”. Hay una diferencia entre el trozo de memoria que reservamos para contener 10 enteros y los símbolos (expresiones) que usamos para referirnos a ese trozo de memoria. Podemos referirnos a esto con la expresión a . También podemos crear un puntero a esa matriz:

 int (*ptr)[10] = &a; 

La expresión *ptr también tiene el tipo “matriz de 10 elementos de int “, y se refiere al mismo fragmento de memoria al que se refiere a a.

C no trata las expresiones de matriz ( a , *ptr ) como expresiones de otros tipos, y una de las diferencias es que una expresión de tipo de matriz puede no ser el objective de una asignación. No puede reasignar a para referirse a un objeto de matriz diferente (lo mismo para la expresión *ptr ). Puede asignar un nuevo valor a a[i] o (*ptr)[i] (cambiar el valor de cada elemento de la matriz), y puede asignar ptr para que apunte a una matriz diferente:

 int b[10], c[10]; ..... ptr = &b; ..... ptr = &c; 

En cuanto a la segunda pregunta …

Un tipo incompleto carece de información de tamaño; declaraciones como

 struct foo; int bar[]; union bletch; 

todos crean tipos incompletos porque no hay suficiente información para que el comstackdor determine la cantidad de almacenamiento que se reservará para un objeto de ese tipo. No puedes crear objetos de tipo incompleto; por ejemplo, no puedes declarar

 struct foo myFoo; 

a menos que complete la definición de struct foo . Sin embargo, puede crear punteros a tipos incompletos; por ejemplo, podrías declarar

 struct foo *myFooPtr; 

sin completar la definición de struct foo porque un puntero solo almacena la dirección del objeto, y no necesita saber el tamaño del tipo para eso. Esto hace posible definir tipos autorreferenciales como

 struct node { T key; // for any type T Q val; // for any type Q struct node *left; struct node *right; }; 

La definición de tipo para struct node no está completa hasta que lleguemos a ese cierre } . Como podemos declarar un puntero a un tipo incompleto, estamos bien. Sin embargo, no pudimos definir la estructura como

 struct node { ... // same as above struct node left; struct node right; }; 

porque el tipo no está completo cuando declaramos los miembros left y right , y también porque cada miembro left y right contendría cada uno sus propios miembros, cada uno de los cuales contendría los miembros left y right , y y así sucesivamente.

Eso es genial para estructuras y uniones, pero ¿qué pasa?

 int bar[]; 

???

Hemos declarado la bar símbolos e indicado que será un tipo de matriz, pero el tamaño es desconocido en este momento. Eventualmente , tendremos que definirlo con un tamaño, pero de esta forma el símbolo se puede usar en contextos donde el tamaño de la matriz no es significativo o necesario. Sin embargo, no tengo un buen ejemplo no artificial de la parte superior de mi cabeza para ilustrar esto.

EDITAR

Respondiendo a los comentarios aquí, ya que no habrá espacio en la sección de comentarios para lo que quiero escribir (estoy en un estado de ánimo detallado esta noche). Tu preguntaste:

¿Significa que todas las variables son expresión?

Significa que cualquier variable puede ser una expresión o parte de una expresión. Así es como el estándar de lenguaje define el término expresión :

6.5 Expresiones
1 Una expresión es una secuencia de operadores y operandos que especifica el cálculo de un valor, o que designa un objeto o una función, o que genera efectos secundarios, o que realiza una combinación de los mismos.

Por ejemplo, la variable a por sí misma cuenta como una expresión; designa el objeto de matriz que definimos para contener 10 valores enteros. También evalúa la dirección del primer elemento de la matriz. La variable a también puede ser parte de una expresión más grande como a[i] ; el operador es el operador de subíndice [] y los operandos son las variables a e i . Esta expresión designa un único miembro de la matriz y evalúa el valor almacenado de forma correcta en ese miembro. Esa expresión a su vez puede ser parte de una expresión más grande como a[i] = 0 .

Y también déjenme aclarar que, en la statement int a [10], ¿a [] representa tipo de matriz

Sí exactamente.

En C, las declaraciones se basan en los tipos de expresiones, en lugar de los tipos de objetos. Si tiene una variable simple llamada y que almacena un valor int , y desea acceder a ese valor, simplemente use y en una expresión, como

 x = y; 

El tipo de la expresión y es int , entonces la statement de y está escrita

 int y; 

Si, por otro lado, tiene una matriz de valores int , y desea acceder a un elemento específico, usaría el nombre de la matriz y un índice junto con el operador de subíndices para acceder a ese valor, como

 x = a[i]; 

El tipo de la expresión a[i] es int , por lo que la statement de la matriz se escribe como

 int arr[N]; // for some value N. 

La ” int -ness” de arr viene dada por el especificador de tipo int ; el “array-ness” de arr viene dado por el declarador arr[N] . El declarador nos da el nombre del objeto que se declara ( arr ) junto con algún tipo de información adicional no proporcionada por el especificador de tipo (“es una matriz de elementos N”). La statement “lee” como

  a -- a a[N] -- is an N-element array int a[N]; -- of int 

EDIT 2

Y después de todo eso, todavía no te he contado la verdadera razón por la cual las expresiones de matriz son valores no modificables. Así que aquí hay otro capítulo de este libro de una respuesta.

C no surgió completamente de la mente de Dennis Ritchie; se derivó de un lenguaje anterior conocido como B (que se derivó de BCPL). 1 B era un lenguaje “sin tipo”; no tenía diferentes tipos para enteros, flotantes, texto, registros, etc. En cambio, todo era simplemente una palabra de longitud fija o “celda” (esencialmente un entero sin signo). La memoria fue tratada como una matriz lineal de celdas. Cuando asignó una matriz en B, como

 auto V[10]; 

el comstackdor asignó 11 celdas; 10 celdas contiguas para la matriz misma, más una celda que estaba vinculada a V que contiene la ubicación de la primera celda:

  +----+ V: | | -----+ +----+ | ... | +----+ | | | <----+ +----+ | | +----+ | | +----+ | | +----+ ... 

Cuando Ritchie estaba agregando tipos de struct a C, se dio cuenta de que este arreglo le estaba causando algunos problemas. Por ejemplo, quería crear un tipo de estructura para representar una entrada en un archivo o tabla de directorio:

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

Quería que la estructura no solo describiera la entrada de manera abstracta, sino que también representara los bits en la entrada real de la tabla de archivos, que no tenía una celda o palabra extra para almacenar la ubicación del primer elemento en la matriz. Así que se deshizo de él: en lugar de reservar una ubicación para almacenar la dirección del primer elemento, escribió C de modo que la dirección del primer elemento se computaría cuando se evaluara la expresión de la matriz.

Es por eso que no puedes hacer algo como

 int a[N], b[N]; a = b; 

porque tanto a como b evalúan los valores del puntero en ese contexto; es equivalente a escribir 3 = 4 . No hay nada en la memoria que almacena la dirección del primer elemento en la matriz; el comstackdor simplemente lo calcula durante la fase de traducción.


1. Todo esto está tomado del artículo El desarrollo del lenguaje C

El término “lvalor del tipo de matriz” se refiere literalmente al objeto matriz como un lvalor del tipo de matriz, es decir, el objeto matriz como un todo . Este lvalue no es modificable en su totalidad, ya que no existe una operación legal que pueda modificarlo en su totalidad. De hecho, las únicas operaciones que puede realizar en un lvalue de tipo array son: unary & (address of), sizeof y conversión implícita a tipo de puntero. Ninguna de estas operaciones modifica la matriz, por lo que los objetos de matriz no son modificables.

a[i] no funciona con lvalue del tipo de matriz. a[i] designa un objeto int : el elemento i-ésimo de la matriz a . La semántica de esta expresión (si está explícitamente explicada) es: *((int *) a + i) . El primer paso – (int *) a – ya convierte el lvalue del tipo de matriz en un valor r de tipo int * . En este punto, el lvalue del tipo de matriz está fuera de lugar para siempre.

El tipo incompleto es un tipo cuyo tamaño aún no se conoce. Por ejemplo: un tipo de estructura que ha sido declarado pero no definido, un tipo de matriz con un tamaño no especificado, el tipo de void .

Un tipo incompleto es un tipo que está declarado pero no definido, por ejemplo struct Foo; .

Siempre puede asignar elementos de matriz individuales (suponiendo que no sean const ). Pero no puedes asignarle algo a toda la matriz.

C y C ++ son bastante confusos en algo como int a[10] = {0, 1, 2, 3}; no es una tarea, sino una inicialización, aunque se parece mucho a una tarea.

Esto está bien (inicialización):

 int a[10] = {0, 1, 2, 3}; 

Esto no funciona en C / C ++:

 int a[10]; a = {0, 1, 2, 3}; 

Suponiendo que a es una matriz de ints, a[10] no es una matriz. Es un int .

a = {0} sería ilegal.

Recuerde que el valor de una matriz es en realidad la dirección (puntero) de su primer elemento. Esta dirección no puede ser modificada. Asi que

 int a[10], b[10]; a = b 

es ilegal.

Por supuesto, no tiene nada que ver con la modificación del contenido de la matriz como en a[1] = 3