una tamaño pixeles papel oficio mide medidas hoja formato cuanto c language-lawyer c99 undefined-behavior

tamaño - ¿A[a[0]]=1 produce un comportamiento indefinido?



tamaño hoja a4 en pixeles (5)

¿Este código C99 produce un comportamiento indefinido?

#include <stdio.h> int main() { int a[3] = {0, 0, 0}; a[a[0]] = 1; printf("a[0] = %d/n", a[0]); return 0; }

En el enunciado a[a[0]] = 1; , a[0] es leído y modificado.

Miré n1124 borrador de ISO / IEC 9899. Dice (en 6.5 Expresiones):

Entre el punto de secuencia anterior y el siguiente, un objeto tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión. Además, el valor anterior se leerá solo para determinar el valor que se almacenará.

No menciona la lectura de un objeto para determinar el objeto a modificar. Por lo tanto, esta declaración podría producir un comportamiento indefinido.

Sin embargo, lo siento extraño. ¿Esto realmente produce un comportamiento indefinido?

(También quiero saber sobre este problema en otras versiones de ISO C).


¿Este código C99 produce un comportamiento indefinido?

No. No producirá un comportamiento indefinido. a[0] se modifica solo una vez entre dos puntos de secuencia (el primer punto de secuencia se encuentra al final del inicializador int a[3] = {0, 0, 0}; y el segundo es después de la expresión completa a[a[0]] = 1 ).

No menciona la lectura de un objeto para determinar el objeto a modificar. Por lo tanto, esta declaración podría producir un comportamiento indefinido.

Un objeto puede leerse más de una vez para modificarse y es un comportamiento perfectamente definido. Mira este ejemplo

int x = 10; x = x*x + 2*x + x%5;

La segunda declaración de la cita dice:

Además, el valor anterior se leerá solo para determinar el valor que se almacenará.

Toda la x en la expresión anterior se lee para determinar el valor del objeto x sí.

NOTA: Tenga en cuenta que hay dos partes de la cita mencionadas en la pregunta. La primera parte dice: Entre el punto de secuencia anterior y el siguiente, un objeto tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión. y
por lo tanto la expresión como

i = i++;

viene bajo UB (dos modificaciones entre los puntos de secuencia anteriores y siguientes).

La segunda parte dice: Además, el valor anterior se leerá solo para determinar el valor que se almacenará. y, por lo tanto, las expresiones como

a[i++] = i; j = (i = 2) + i;

invocar a UB. En ambas expresiones, i se modifica solo una vez entre los puntos de secuencia anteriores y siguientes, pero la lectura del extremo derecho no determina el valor que se almacenará en i .

En el estándar C11, esto se ha cambiado a

6.5 Expresiones:

Si un efecto secundario en un objeto escalar no está secuenciado en relación con un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido. [...]

En la expresión a[a[0]] = 1 , solo hay un efecto secundario a a[0] y el cálculo del valor del índice a[0] se secuencia antes del cálculo del valor de a[a[0]] .


el valor anterior se leerá solo para determinar el valor que se almacenará.

Esto es un poco vago y causó confusión, que es en parte por lo que C11 lo lanzó e introdujo un nuevo modelo de secuencia.

Lo que está tratando de decir es que: si se garantiza que leer el valor anterior se produce antes de tiempo que escribir el nuevo valor, entonces está bien. De lo contrario, es UB. Y, por supuesto, es un requisito que el nuevo valor se calcule antes de escribirse.

(¡Por supuesto, algunos encontrarán que la descripción que acabo de escribir es más vaga que el texto estándar!)

Por ejemplo, x = x + 5 es correcto porque no es posible calcular x + 5 sin saber primero x . Sin embargo, a[i] = i++ está mal porque no es necesaria la lectura de i en el lado izquierdo para calcular el nuevo valor que se almacenará en i . (Las dos lecturas de i se consideran por separado).

De vuelta a su código ahora. Creo que es un comportamiento bien definido porque la lectura de a[0] para determinar el índice de la matriz está garantizada antes de la escritura.

No podemos escribir hasta que hayamos determinado dónde escribir. Y no sabemos dónde escribir hasta después de leer a[0] . Por lo tanto, la lectura debe venir antes de la escritura, por lo que no hay UB.

Alguien comentó sobre los puntos de secuencia. En C99 no hay un punto de secuencia en esta expresión, por lo que los puntos de secuencia no entran en esta discusión.


C99 presenta una enumeración de todos los puntos de secuencia en el anexo C. Hay uno al final de

a[a[0]] = 1;

porque es una declaración de expresión completa, pero no hay puntos de secuencia dentro. Aunque la lógica dicta que la subexpresión a[0] debe evaluarse primero, y el resultado utilizado para determinar a qué elemento de matriz se asigna el valor, las reglas de secuencia no lo aseguran. Cuando el valor inicial de a[0] es 0 , a[0] se lee y se escribe entre dos puntos de secuencia, y la lectura no tiene el propósito de determinar qué valor escribir. Según C99 6.5 / 2, el comportamiento de evaluar la expresión es, por lo tanto, indefinido, pero en la práctica no creo que deba preocuparse por ello.

C11 es mejor en este sentido. La sección 6.5, párrafo (1) dice

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. Los cálculos del valor de los operandos de un operador se secuencian antes del cálculo del valor del resultado del operador.

Note en particular la segunda oración, que no tiene análogo en C99. Puede pensar que eso sería suficiente, pero no lo es. Se aplica a los cálculos de valores , pero no dice nada sobre la secuencia de efectos secundarios en relación con los cálculos de valores. Actualizar el valor del operando izquierdo es un efecto secundario, por lo que la oración adicional no se aplica directamente.

Sin embargo, C11 se nos presenta en este caso, ya que las especificaciones para los operadores de asignación proporcionan la secuencia necesaria (C11 6.5.16 (3)):

[...] El efecto secundario de actualizar el valor almacenado del operando izquierdo se secuencia después de los cálculos del valor de los operandos izquierdo y derecho. Las evaluaciones de los operandos no tienen secuencia.

(Por el contrario, C99 solo dice que la actualización del valor almacenado del operando izquierdo ocurre entre los puntos de secuencia anterior y siguiente.) Con las secciones 6.5 y 6.5.16 juntas, C11 proporciona una secuencia bien definida: el [] interno es evaluado antes del exterior [] , que se evalúa antes de actualizar el valor almacenado. Esto satisface la versión de C11 de 6.5 (2), por lo que en C11, se define el comportamiento de evaluar la expresión.


El valor está bien definido, a menos que a[0] contenga un valor que no sea un índice de matriz válido (es decir, en su código no es negativo y no excede 3 ). Puedes cambiar el código a uno más legible y equivalente

index = a[0]; a[index] = 1; /* still UB if index < 0 || index >= 3 */

En la expresión a[a[0]] = 1 es necesario evaluar a[0] primero. Si a[0] resulta ser cero, entonces se modificará a[0] . Pero no hay forma de que un compilador (sin cumplir con el estándar) cambie el orden de las evaluaciones y modifique a[0] antes de intentar leer su valor.


Un efecto secundario incluye la modificación de un objeto 1 .

El estándar C dice que el comportamiento no está definido si un efecto secundario sobre el objeto no está secuenciado con un efecto secundario sobre el mismo objeto o un cálculo de valor utilizando el valor del mismo objeto 2 .

El objeto a[0] en esta expresión se modifica (efecto secundario) y su valor (cálculo del valor) se utiliza para determinar el índice. Parece que esta expresión produce un comportamiento indefinido:

a[a[0]] = 1

Sin embargo, el texto en los operadores de asignación en el estándar explica que el cálculo del valor de los operandos izquierdo y derecho del operador = , se secuencia antes de que se modifique el operando izquierdo 3 .

El comportamiento se define así, ya que la primera regla 1 no se viola, porque la modificación (efecto secundario) se secuencia después del cálculo del valor del mismo objeto.

1 (Citado de ISO / IEC 9899: 201x 5.1.2.3 Ejecución del programa 2):
Acceder a un objeto volátil, modificar un objeto, modificar un archivo o llamar a una función que realice cualquiera de esas operaciones son todos efectos secundarios, que son cambios en el estado del entorno de ejecución.

2 (Citado de ISO / IEC 9899: 201x 6.5 Expresiones 2):
Si un efecto secundario en un objeto escalar no está secuenciado en relación con un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.

3 (Citado de ISO / IEC 9899: 201x 6.5.16 Operadores de asignación 3):
El efecto secundario de actualizar el valor almacenado del operando izquierdo se secuencia después de los cálculos del valor de los operandos izquierdo y derecho. Las evaluaciones de los operandos no tienen secuencia.