Comportamiento definido para expresiones

7 minutos de lectura

avatar de usuario
máscara de bits

El estándar C99 dice en $6.5.2.

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

(énfasis mío)

Continúa señalando que el siguiente ejemplo es válido (lo que parece obvio al principio)

a[i] = i;

Si bien no establece explícitamente qué a y i están.

Aunque creo que no, me gustaría saber si este ejemplo cubre el siguiente caso:

int i = 0, *a = &i;
a[i] = i;

Esta voluntad no cambiar el valor de ipero acceda al valor de i para determinar la dirección donde poner el valor. ¿O es irrelevante que asignemos un valor a i que ya está guardado en i? Por favor, arroja algo de luz.


Pregunta extra; Qué pasa a[i]++ o a[i] = 1?

  • Por supuesto que puedes ser tonto, y C te permite hacer esto. ¿pero por qué?

    – Ed Heal

    29 de enero de 2012 a las 19:50

  • Mantenga su código simple. Fácil. La próxima persona puede necesitar leer estas cosas. Y esa persona puedes ser tú. Bueno, vaya y cuélguese o tenga problemas con otra persona pobre. Tu elección

    – Ed Heal

    29 de enero de 2012 a las 19:57

La primera oración:

Entre el punto de secuencia anterior y el siguiente, el valor almacenado de un objeto se modificará como máximo una vez mediante la evaluación de una expresión.

es lo suficientemente claro. El lenguaje no impone un orden de evaluación en las subexpresiones a menos que haya un punto de secuencia entre ellas, y en lugar de requerir algunos orden de evaluación no especificado, dice que modificar un objeto dos veces produce un comportamiento indefinido. Esto permite una optimización agresiva al mismo tiempo que permite escribir código que sigue las reglas.

La siguiente frase:

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

parece poco intuitivo a primera (y segunda) mirada; ¿Por qué el propósito por el cual se lee un valor debería afectar si una expresión tiene un comportamiento definido?

Pero lo que refleja es que si una subexpresión B depende del resultado de una subexpresión A, entonces A debe evaluarse antes de B puede ser evaluado. Los estándares C90 y C99 no establecen esto explícitamente.

Una violación más clara de esa oración, dada en un ejemplo en la nota al pie, es:

a[i++] = i; /* undefined behavior */

Asumiendo que a es un objeto de matriz declarado y i es un objeto entero declarado (sin puntero ni truco de macro), ningún objeto se modifica más de una vez, por lo que no viola la primera oración. Pero la evaluación de i++ en el LHS determina qué objeto se va a modificar, y la evaluación de i en el RHS determina el valor que se almacenará en ese objeto, y el orden relativo de la operación de lectura en el RHS y la operación de escritura en el LHS no está definido. Una vez más, el lenguaje podría haber requerido que las subexpresiones se evaluaran en algún orden no especificado, pero en su lugar dejó el comportamiento completo sin definir, para permitir una optimización más agresiva.

En tu ejemplo:

int i = 0, *a = &i;
a[i] = i; /* undefined behavior (I think) */

el valor anterior de i se lee tanto para determinar el valor que se almacenará y para determinar en qué objeto se almacenará. Dado que a[i] se refiere a i (pero solo porque i==0), modificando el valor de i cambiaría el objeto al que el lvalue a[i] se refiere. Sucede en este caso que el valor almacenado en i es el mismo que el valor que ya estaba almacenado allí (0), pero el estándar no hace una excepción para las tiendas que almacenan el mismo valor. Creo que el comportamiento es indefinido. (Por supuesto, el ejemplo en el estándar no tenía la intención de cubrir este caso; implícitamente asume que a es un objeto de matriz declarado no relacionado con i.)

En cuanto al ejemplo que la norma dice que está permitido:

int a[10], i = 0; /* implicit, not stated in standard */
a[i] = i;

una pudo interpretar el estándar para decir que no está definido. Pero creo que la segunda oración, que se refiere al “valor anterior”, se aplica solo al valor de un objeto modificado por la expresión. i nunca se modifica por la expresión, por lo que no hay conflicto. El valor de i se utiliza tanto para determinar el objeto que se modificará mediante la asignación como el valor que se almacenará allí, pero está bien, ya que el valor de i en sí mismo nunca cambia. El valor de i no es “el valor anterior”, es solo el valor.

El estándar C11 tiene un nuevo modelo para este tipo de evaluación de expresiones, o mejor dicho, expresa el mismo modelo en diferentes palabras. En lugar de “puntos de secuencia”, habla de efectos secundarios secuenciados uno antes o después del otro, o no secuenciados entre sí. Hace explícita la idea de que si una subexpresión B depende del resultado de una subexpresión A, entonces A debe evaluarse antes de B puede ser evaluado.

En el borrador N1570la sección 6.5 dice:

1 año 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 de valor de los operandos de un operador se secuencian antes del cálculo de valor del resultado del operador.

2 Si un efecto secundario en un objeto escalar no tiene secuencia 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 no está definido. Si hay varios ordenamientos permitidos de las subexpresiones de una expresión, el comportamiento no está definido si se produce un efecto secundario sin secuencia en cualquiera de los ordenamientos.

3 La agrupación de operadores y operandos se indica mediante la sintaxis. Salvo que se especifique más adelante, los efectos secundarios y los cálculos de valor de las subexpresiones no están secuenciados.

  • @hvd: el resultado de evaluar la subexpresión i++ (junto con el resultado de evaluar a) determina qué objeto se va a modificar por la asignación. Ese resultado resulta ser el valor previo de i.

    –Keith Thompson

    8 de febrero de 2012 a las 0:01

  • La falta de definición del comportamiento se remonta a antes de las ideas modernas de optimización. Sospecho que las razones principales por las que no está definido son (1) hay muchas situaciones en las que las “consecuencias naturales de la plataforma” de un alias inesperado podrían causar valores distorsionados arbitrariamente, especialmente cuando se opera con valores de varias palabras “en el lugar”, y se percibía poco beneficio de distinguir los valores distorsionados de “cualquier cosa puede pasar”; (2) una función para interceptar dichos usos podría ser útil, pero dado que el estándar C no dice nada sobre lo que pueden y no pueden hacer las trampas, permitir trampas significa permitir Comportamiento indefinido.

    – Super gato

    28/04/2015 a las 19:45

Leer el valor de un objeto para determinar donde almacenarlo no cuenta como “determinar el valor a almacenar”. Esto significa que el único punto de discusión puede ser si estamos o no “modificando” el objeto. i: si lo somos, es indefinido; si no lo somos, está bien.

¿Almacenar el valor 0 en un objeto que ya contiene el valor 0 cuenta como “modificar el valor almacenado”? Según la sencilla definición en inglés de “modificar”, tendría que decir que no; dejar algo sin cambios es lo contrario de modificarlo.

Sin embargo, está claro que esto sería un comportamiento indefinido:

int i = 0, *a = &i;
a[i] = 1;

Aquí no puede haber duda de que el valor almacenado se lee con un propósito distinto al de determinar el valor a almacenar (el valor a almacenar es una constante), y que el valor de i es modificado.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad