En lenguaje C, si inicializa una matriz como esta:
int a[5] = {1,2};
entonces todos los elementos de la matriz que no se inicializan explícitamente se inicializarán implícitamente con ceros.
Pero, si inicializo una matriz como esta:
int a[5]={a[2]=1};
printf("%d %d %d %d %d\n", a[0], a[1],a[2], a[3], a[4]);
salida:
1 0 1 0 0
No entiendo, ¿por qué a[0]
imprime en 1
lugar de 0
? ¿Es un comportamiento indefinido?
Nota: esta pregunta se hizo en una entrevista.
a[2]=1
evalúa como1
.a[2] = 1
es1
, pero no estoy seguro de si se le permite tomar el resultado de una expresión de inicializador designada como el valor del primer elemento. El hecho de que haya agregado la etiqueta de abogado significa que creo que necesitamos una respuesta citando el estándar.Respuestas:
TL; DR: No creo que el comportamiento de
int a[5]={a[2]=1};
esté bien definido, al menos en C99.La parte divertida es que lo único que tiene sentido para mí es la parte por la que estás preguntando:
a[0]
está configurada para1
porque el operador de asignación devuelve el valor que se asignó. Es todo lo demás lo que no está claro.Si el código hubiera sido así
int a[5] = { [2] = 1 }
, todo hubiera sido fácil: esa es una configuración de inicializador designadaa[2]
para1
y todo lo demás para0
. Pero con{ a[2] = 1 }
tenemos un inicializador no designado que contiene una expresión de asignación, y caemos en un agujero de conejo.Esto es lo que encontré hasta ahora:
a
debe ser una variable local.a[2] = 1
no es una expresión constante, por lo quea
debe tener almacenamiento automático.a
está dentro del alcance en su propia inicialización.El declarador es
a[5]
, por lo que las variables están dentro del alcance en su propia inicialización.a
está vivo en su propia inicialización.Hay un punto de secuencia después
a[2]=1
.Tenga en cuenta que, por ejemplo, en
int foo[] = { 1, 2, 3 }
la{ 1, 2, 3 }
parte hay una lista de inicializadores entre llaves, cada uno de los cuales tiene un punto de secuencia después.La inicialización se realiza en el orden de la lista de inicializadores.
Sin embargo, las expresiones de inicializador no se evalúan necesariamente en orden.
Sin embargo, eso todavía deja algunas preguntas sin respuesta:
¿Son los puntos de secuencia siquiera relevantes? La regla básica es:
a[2] = 1
es una expresión, pero la inicialización no lo es.Esto se contradice levemente con el Anexo J:
El anexo J dice que cualquier modificación cuenta, no solo modificaciones por expresiones. Pero dado que los anexos no son normativos, probablemente podamos ignorarlo.
¿Cómo se secuencian las inicializaciones de subobjetos con respecto a las expresiones del inicializador? ¿Se evalúan primero todos los inicializadores (en algún orden) y luego los subobjetos se inicializan con los resultados (en el orden de la lista de inicializadores)? ¿O se pueden intercalar?
Creo que
int a[5] = { a[2] = 1 }
se ejecuta de la siguiente manera:a
se asigna cuando se ingresa su bloque contenedor. El contenido es indeterminado en este momento.a[2] = 1
), seguido de un punto de secuencia. Esto almacena1
ena[2]
y retornos1
.1
se usa para inicializara[0]
(el primer inicializador inicializa el primer subobjeto).Pero aquí las cosas se ponen borrosa debido a que los elementos restantes (
a[1]
,a[2]
,a[3]
,a[4]
) se supone que deben ser inicializado a0
, pero no está claro cuándo: ¿Ocurre antes de quea[2] = 1
se evalúa? Si es así,a[2] = 1
¿"ganaría" y sobrescribiríaa[2]
, pero esa asignación tendría un comportamiento indefinido porque no hay un punto de secuencia entre la inicialización cero y la expresión de asignación? ¿Son los puntos de secuencia incluso relevantes (ver arriba)? ¿O ocurre la inicialización cero después de evaluar todos los inicializadores? Si es así,a[2]
debería acabar siendo0
.Dado que el estándar C no define claramente lo que sucede aquí, creo que el comportamiento no está definido (por omisión).
fuente
a[0]
subobjeto antes de evaluar su inicializador, y la evaluación de cualquier inicializador incluye un punto de secuencia (porque es una "expresión completa"). Por tanto, creo que modificar el subobjeto que estamos inicializando es un juego limpio.Presumiblemente se
a[2]=1
inicializaa[2]
primero y el resultado de la expresión se usa para inicializara[0]
.Desde N2176 (borrador C17):
Entonces parecería que la salida
1 0 0 0 0
también habría sido posible.Conclusión: No escriba inicializadores que modifiquen la variable inicializada sobre la marcha.
fuente
{...}
expresión que se inicializaa[2]
en0
y la subexpresióna[2]=1
que se inicializaa[2]
en1
.{...}
es una lista de inicializadores entre corchetes. No es una expresión.Creo que el estándar C11 cubre este comportamiento y dice que el resultado no está especificado , y no creo que C18 haya realizado cambios relevantes en esta área.
El lenguaje estándar no es fácil de analizar. La sección relevante de la norma es §6.7.9 Inicialización . La sintaxis se documenta como:
Tenga en cuenta que uno de los términos es expresión de asignación , y dado
a[2] = 1
que indudablemente es una expresión de asignación, se permite dentro de los inicializadores para matrices con duración no estática:Uno de los párrafos clave es:
Y otro párrafo clave es:
Estoy bastante seguro de que el párrafo 23 indica que la notación en la pregunta:
conduce a un comportamiento no especificado. La asignación a
a[2]
es un efecto secundario, y el orden de evaluación de las expresiones está secuenciado indeterminadamente entre sí. En consecuencia, no creo que haya una forma de apelar al estándar y afirmar que un compilador en particular está manejando esto correcta o incorrectamente.fuente
Mi comprensión
a[2]=1
devuelve el valor 1, por lo que el código se convierteint a[5]={1}
asignar valor a [0] = 1Por lo tanto, imprime 1 para un [0]
Por ejemplo
fuente
Intento dar una respuesta breve y sencilla al acertijo:
int a[5] = { a[2] = 1 };
a[2] = 1
se establece. Eso significa que la matriz dice:0 0 1 0 0
{ }
entre corchetes, que se usan para inicializar la matriz en orden, toma el primer valor (que es1
) y lo establece ena[0]
. Es como siint a[5] = { a[2] };
se quedara, donde ya llegamosa[2] = 1
. La matriz resultante es ahora:1 0 1 0 0
Otro ejemplo:
int a[6] = { a[3] = 1, a[4] = 2, a[5] = 3 };
- Aunque el orden es algo arbitrario, asumiendo que va de izquierda a derecha, iría en estos 6 pasos:fuente
A = B = C = 5
no es una declaración (o inicialización). Es una expresión normal que se analizaA = (B = (C = 5))
porque el=
operador es asociativo a la derecha. Eso realmente no ayuda a explicar cómo funciona la inicialización. La matriz realmente comienza a existir cuando se ingresa el bloque en el que está definida, lo que puede ser mucho antes de que se ejecute la definición real.a[2] = 1
que se aplique el efecto secundario de la expresión del inicializador? El resultado observado es como si lo fuera, pero el estándar no parece especificar que ese debería ser el caso. Ese es el centro de la controversia, y esta respuesta lo pasa por alto por completo.La asignación
a[2]= 1
es una expresión que tiene el valor1
y usted esencialmente escribióint a[5]= { 1 };
(con el efecto secundario que tambiéna[2]
se le asigna1
).fuente
Creo que ese
int a[5]={ a[2]=1 };
es un buen ejemplo para un programador que se dispara a sí mismo en su propio pie.Podría estar tentado a pensar que lo que quería decir era
int a[5]={ [2]=1 };
cuál sería un elemento de configuración de inicializador designado C99 2 a 1 y el resto a cero.En el raro caso de que realmente
int a[5]={ 1 }; a[2]=1;
quisieras decirlo , sería una forma divertida de escribirlo. De todos modos, esto es a lo que se reduce su código, aunque algunos aquí señalaron que no está bien definido cuándoa[2]
se ejecuta realmente la escritura . El problema aquí es quea[2]=1
no es un inicializador designado, sino una asignación simple que en sí misma tiene el valor 1.fuente