¿Por qué estas construcciones usan un comportamiento indefinido antes y después del incremento?


#include <stdio.h>

int main(void)
{
   int i = 0;
   i = i++ + ++i;
   printf("%d\n", i); // 3

   i = 1;
   i = (i++);
   printf("%d\n", i); // 2 Should be 1, no ?

   volatile int u = 0;
   u = u++ + ++u;
   printf("%d\n", u); // 1

   u = 1;
   u = (u++);
   printf("%d\n", u); // 2 Should also be one, no ?

   register int v = 0;
   v = v++ + ++v;
   printf("%d\n", v); // 3 (Should be the same as u ?)

   int w = 0;
   printf("%d %d %d\n", w++, ++w, w); // shouldn't this print 0 2 2

   int x[2] = { 5, 8 }, y = 0;
   x[y] = y ++;
   printf("%d %d\n", x[0], x[1]); // shouldn't this print 0 8? or 5 0?
}
Author: jww, 2009-06-04

14 answers

C tiene el concepto de comportamiento indefinido, es decir, algunas construcciones del lenguaje son sintácticamente válidas pero no se puede predecir el comportamiento cuando se ejecuta el código.

Hasta donde yo sé, el estándar no dice explícitamente por qué existe el concepto de comportamiento indefinido. En mi mente, es simplemente porque los diseñadores del lenguaje querían que hubiera un cierto margen de maniobra en la semántica, en lugar de requerir que todas las implementaciones manejen el desbordamiento de enteros de la misma manera, lo que sería muy es probable que impongan costos de rendimiento graves, simplemente dejaron el comportamiento indefinido de modo que si escribe código que causa desbordamiento de enteros, puede suceder cualquier cosa.

Entonces, con eso en mente, ¿por qué son estos "problemas"? El lenguaje dice claramente que ciertas cosas conducen a un comportamiento indefinido. No hay ningún problema, no hay ningún "debería" involucrados. Si el comportamiento indefinido cambia cuando se declara una de las variables involucradas volatile, eso no prueba ni cambia nada. Es undefined; no puedes razonar sobre el comportamiento.

Tu ejemplo más interesante, el que tiene

u = (u++);

Es un ejemplo de libro de texto de comportamiento indefinido (véase la entrada de Wikipedia en puntos de secuencia).

 524
Author: unwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-16 09:38:33

Simplemente compila y desensambla tu línea de código, si estás tan inclinado a saber exactamente cómo es que obtienes lo que estás obteniendo.

Esto es lo que consigo en mi máquina, junto con lo que creo que está pasando:

$ cat evil.c
void evil(){
  int i = 0;
  i+= i++ + ++i;
}
$ gcc evil.c -c -o evil.bin
$ gdb evil.bin
(gdb) disassemble evil
Dump of assembler code for function evil:
   0x00000000 <+0>:   push   %ebp
   0x00000001 <+1>:   mov    %esp,%ebp
   0x00000003 <+3>:   sub    $0x10,%esp
   0x00000006 <+6>:   movl   $0x0,-0x4(%ebp)  // i = 0   i = 0
   0x0000000d <+13>:  addl   $0x1,-0x4(%ebp)  // i++     i = 1
   0x00000011 <+17>:  mov    -0x4(%ebp),%eax  // j = i   i = 1  j = 1
   0x00000014 <+20>:  add    %eax,%eax        // j += j  i = 1  j = 2
   0x00000016 <+22>:  add    %eax,-0x4(%ebp)  // i += j  i = 3
   0x00000019 <+25>:  addl   $0x1,-0x4(%ebp)  // i++     i = 4
   0x0000001d <+29>:  leave  
   0x0000001e <+30>:  ret
End of assembler dump.

(I... supongamos que la instrucción 0x00000014 era algún tipo de optimización del compilador?)

 73
Author: badp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-01 23:21:50

Creo que las partes relevantes del estándar C99 son Expresiones 6.5, §2

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

Y 6.5.16 Operadores de asignación, §4:

El orden de evaluación de los operandos no está especificado. Si se intenta modificar el resultado de un operador de asignación o para acceder a él después del siguiente punto de secuencia, el el comportamiento es indefinido.

 55
Author: Christoph,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-06-04 09:42:43

El comportamiento realmente no se puede explicar porque invoca tanto unspecified behavior como undefined behavior, por lo que no podemos hacer ninguna predicción general sobre este código, aunque si lee El trabajo de Olve Maudal como Deep C y Unspecified and Undefined a veces puede hacer buenas conjeturas en casos muy específicos con un compilador y un entorno específicos, pero por favor no lo hagas cerca de la producción.

Así que seguimos adelante a comportamiento no especificado , en proyecto de norma c99 sección 6.5 párrafo 3 dice ( énfasis mío):

La agrupación de operadores y operandos se indica mediante la sintaxis.74) Salvo lo especificado más tarde (para la función-llamada (), &&, ||, ?:, y operadores de coma), el orden de evaluación de las subexpresiones y el orden en el que se producen los efectos secundarios no están especificados.

Así que cuando tenemos una línea como esto:

i = i++ + ++i;

No sabemos si i++ o ++i serán evaluados primero. Esto es principalmente para darle al compilador mejores opciones para la optimización.

También tenemos comportamiento indefinido aquí también ya que el programa está modificando variables(i, u, etc..) más de una vez entre puntos de secuencia. Del párrafo del proyecto de sección estándar 6.52( énfasis mío):

Entre el anterior y el siguiente punto de secuencia 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á únicamente para determinar el valor que se almacenará .

Cita los siguientes ejemplos de código como indefinidos:

i = ++i + 1;
a[i++] = i; 

En todos estos ejemplos el código está intentando modificar un objeto más de una vez en el mismo punto de secuencia, que terminará con el ; en cada uno de estos casos:

i = i++ + ++i;
^   ^       ^

i = (i++);
^    ^

u = u++ + ++u;
^   ^       ^

u = (u++);
^    ^

v = v++ + ++v;
^   ^       ^

Unspecified behavior is defined in the draft c99 standard in section 3.4.4 as:

Uso de un valor no especificado, u otro comportamiento donde este Estándar Internacional proporciona dos o más posibilidades y no impone más requisitos sobre los que se elige en cualquier instance

Y el comportamiento indefinido se define en la sección 3.4.3 como:

Comportamiento, sobre el uso de un no portable o construcción errónea del programa o de datos erróneos, para los que esta Norma Internacional no impone requisitos

Y señala que:

El posible comportamiento indefinido abarca desde ignorar la situación completamente con resultados impredecibles, hasta comportarse durante la traducción o ejecución del programa de una manera documentada característica del entorno (con o sin la emisión de un mensaje de diagnóstico), hasta terminar una traducción o ejecución (con la emisión de un mensaje de diagnóstico).

 44
Author: Shafik Yaghmour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:44

La mayoría de las respuestas aquí citadas de C standard enfatizando que el comportamiento de estas construcciones son indefinidas. Para entender por qué el comportamiento de estos constructos es indefinido , entendamos estos términos primero a la luz del estándar C11:

Secuenciado: (5.1.2.3)

Dadas dos evaluaciones cualesquiera A y B, si A está secuenciada antes de B, entonces la ejecución de A precederá a la ejecución de B.

Sin secuencia:

Si A no está secuenciado antes o después de B, entonces A y B no están secuenciados.

Las evaluaciones pueden ser una de dos cosas: {[34]]}

  • valor cálculos, que el resultado de una expresión; y
  • efectos secundarios, que son modificaciones de objetos.

Punto de secuencia:

La presencia de un punto de secuencia entre la evaluación de las expresiones A y B implica que cada valor de cálculo de y efecto secundario asociados con A es la secuencia antes de cada valor de cálculo de y efecto secundario asociados con B.

Ahora llegando a la pregunta, para las expresiones como

int i = 1;
i = i++;

La norma dice que:

6.5 Expresiones:

Si un efecto secundario en un objeto escalar es relativo no secuenciado para ya sea un efecto secundario diferente en el mismo objeto escalaro un cálculo de valor usando el valor del mismo objeto escalar, el comportamiento es indefinido. [...]

Por lo tanto, la expresión anterior invoca UB porque dos efectos secundarios en el mismo objeto i no tienen secuencia relativa entre sí. Eso significa que no está secuenciado si el efecto secundario por asignación a i se hará antes o después del efecto secundario por ++.
Dependiendo de ya sea que la asignación ocurra antes o después del incremento, se producirán diferentes resultados y ese es el caso de comportamiento indefinido.

Permite renombrar el i a la izquierda de la asignación ser il y a la derecha de la asignación (en la expresión i++) ser ir, entonces la expresión ser como

il = ir++     // Note that suffix l and r are used for the sake of clarity.
              // Both il and ir represents the same object.  

Un punto importante con respecto al operador Postfix ++ es que:

Solo porque el ++ viene después de la variable no significa que el incremento sucede tarde . El incremento puede ocurrir tan pronto como al compilador le gusta siempre y cuando el compilador se asegure de que el valor original es usado.

Significa que la expresión il = ir++ podría evaluarse como

temp = ir;      // i = 1
ir = ir + 1;    // i = 2   side effect by ++ before assignment
il = temp;      // i = 1   result is 1  

O

temp = ir;      // i = 1
il = temp;      // i = 1   side effect by assignment before ++
ir = ir + 1;    // i = 2   result is 2  

Resultando en dos resultados diferentes 1 y 2 que depende de la secuencia de efectos secundarios por asignación y ++ y por lo tanto invoca UB.

 42
Author: haccks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:24

Otra forma de responder a esto, en lugar de atascarse en detalles arcanos de puntos de secuencia y comportamiento indefinido, es simplemente preguntar, ¿qué se supone que significan? ¿Qué intentaba hacer el programador?

El primer fragmento sobre el que se preguntó, i = i++ + ++i, es claramente una locura en mi libro. Nadie lo escribiría en un programa real, no es obvio lo que hace, no hay ningún algoritmo concebible que alguien podría haber estado tratando de codificar que tendría resultó en esta particular secuencia artificial de operaciones. Y ya que no es obvio para ti y para mí lo que se supone que debe hacer, está bien en mi libro si el compilador no puede averiguar lo que se supone que debe hacer, tampoco.

El segundo fragmento, i = i++, es un poco más fácil de entender. Alguien está claramente tratando de incrementar i, y asignar el resultado de nuevo a i. Pero hay un par de maneras de hacer esto en C. La forma más básica de añadir 1 a i, y asignar el resultado de nuevo a i, es la misma en casi cualquier lenguaje de programación:

i = i + 1

C, por supuesto, tiene un atajo práctico:

i++

Esto significa, "añadir 1 a i, y asignar el resultado de nuevo a i". Así que si construimos una mezcolanza de los dos, escribiendo

i = i++

Lo que realmente estamos diciendo es "añadir 1 a i, y asignar el resultado de nuevo a i, y asignar el resultado de nuevo a i". Estamos confundidos, así que no me molesta demasiado si el compilador también se confunde.

Siendo realistas, la única vez que estos locos las expresiones se escriben cuando la gente las usa como ejemplos artificiales de cómo se supone que funciona++. Y, por supuesto, es importante entender cómo funciona++. Pero una regla práctica para usar ++ es, " Si no es obvio lo que significa una expresión usando++, no la escribas."

Solíamos pasar incontables horas en comp.lang.c discutir expresiones como estas y por qué no están definidas. Dos de mis respuestas más largas, que tratan de explicar realmente por qué, se archivan en el web:

 29
Author: Steve Summit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-26 21:27:39

Si bien es poco probable que los compiladores y procesadores realmente lo hagan, sería legal, bajo el estándar C, para el compilador implementar " i++"con la secuencia:

In a single operation, read `i` and lock it to prevent access until further notice
Compute (1+read_value)
In a single operation, unlock `i` and store the computed value

Aunque no creo que ningún procesador soporte el hardware para permitir que tal cosa se haga de manera eficiente, uno puede imaginar fácilmente situaciones en las que tal comportamiento haría más fácil el código multihilo (por ejemplo, garantizaría que si dos subprocesos intentan realizar la secuencia anterior simultáneamente, i se incrementaría en dos) y no es totalmente inconcebible que algún procesador futuro pueda proporcionar una característica algo así.

Si el compilador escribiera i++ como se indicó anteriormente (legal bajo el estándar) e intercalara las instrucciones anteriores a lo largo de la evaluación de la expresión general (también legal), y si no se diera cuenta de que una de las otras instrucciones tuvo acceso a i, sería posible (y legal) que el compilador generar una secuencia de instrucciones que interbloqueo. Para estar seguro, un compilador casi seguramente detectaría el problema en el caso en que la misma variable i se usa en ambos lugares, pero si una rutina acepta referencias a dos punteros p y q, y usa (*p) y (*q) en la expresión anterior (en lugar de usar i dos veces), el compilador no tendría que reconocer o evitar el punto muerto que ocurriría si la misma dirección del objeto se pasara tanto para p como para q.

 22
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-23 20:06:36

A menudo esta pregunta está vinculada como un duplicado de preguntas relacionadas con código como

printf("%d %d\n", i, i++);

O

printf("%d %d\n", ++i, i++);

O variantes similares.

Mientras que esto también escomportamiento indefinido como ya se ha dicho, hay diferencias sutiles cuando printf() está involucrado cuando se compara con una declaración como:

   x = i++ + i++;

En la siguiente declaración:

printf("%d %d\n", ++i, i++);

El orden de evaluación de argumentos en printf() es sin especificar. Esto significa que las expresiones i++ y ++i podrían evaluarse en cualquier orden. El estándar C11 tiene algunas descripciones relevantes sobre esto:

Anexo J, comportamientos no especificados

El orden en el que el designador de la función, las subexpresiones dentro de los argumentos se evalúan en una llamada a función (6.5.2.2).

3.4.4, comportamiento no especificado

Uso de un valor no especificado, u otro comportamiento donde este Estándar Internacional proporciona dos o más posibilidades e impone no hay más requisitos sobre los que se elige en cualquier caso.

EJEMPLO Un ejemplo de comportamiento no especificado es el orden en el que se evalúan los argumentos de una función.

El comportamiento no especificado en sí NO es un problema. Considere este ejemplo:

printf("%d %d\n", ++x, y++);

Esto también tiene comportamiento no especificado porque el orden de evaluación de ++x y y++ no se especifica. Pero es una declaración perfectamente legal y válida. No hay ningún comportamiento indefinido en esta declaración. Porque las modificaciones (++x y y++) se hacen a distintos objetos.

Lo que hace la siguiente declaración

printf("%d %d\n", ++i, i++);

Como un comportamiento indefinido es el hecho de que estas dos expresiones modificar el mismo object i sin intervenir secuencia punto.


Otro detalle es que la coma involucrada en la llamada printf () es un separador , no el operador de coma.

Esta es una distinción importante porque el operador coma introduce un punto de secuencia entre la evaluación de sus operandos, lo que hace que lo siguiente sea legal:{[28]]}

int i = 5;
int j;

j = (++i, i++);  // No undefined behaviour here because the comma operator 
                 // introduces a sequence point between '++i' and 'i++'

printf("i=%d j=%d\n",i, j); // prints: i=7 j=6

El operador coma evalúa sus operandos de izquierda a derecha y produce solo el valor del último operando. Así que en j = (++i, i++);, ++i incrementos i a 6 y i++ rendimientos de valor antiguo de i (6) que se asigna a j. Entonces i se convierte en 7 debido al post-incremento.

Así que si la coma en la llamada a la función fuera un operador de coma entonces

printf("%d %d\n", ++i, i++);

No Será un problema. Pero invoca comportamiento indefinido porque la coma aquí hay un separador .


Para aquellos que son nuevos en comportamiento indefinido se beneficiaría de leer Lo que Todo Programador de C Debería Saber Sobre Comportamiento Indefinido para entender el concepto y muchas otras variantes de comportamiento indefinido en C.

Este post: Undefined, unspecified and implementation-defined behavior también es relevante.

 18
Author: P.P.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:55:11

El estándar C dice que una variable solo debe asignarse como máximo una vez entre dos puntos de secuencia. Un punto y coma, por ejemplo, es un punto de secuencia.
Así que cada declaración de la forma:

i = i++;
i = i++ + ++i;

Y así sucesivamente violar esa regla. El estándar también dice que el comportamiento es indefinido y no no especificado. Algunos compiladores detectan estos y producen algún resultado, pero esto no es por estándar.

Sin embargo, dos variables diferentes se pueden incrementar entre dos secuencias punto.

while(*src++ = *dst++);

Lo anterior es una práctica común de codificación al copiar/analizar cadenas.

 13
Author: Nikhil Vidhani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-11 12:36:41

Mientras que la sintaxis de expresiones como a = a++ o a++ + a++ es legal, el comportamiento de estas construcciones es indefinido porquedeberá en C estándar no se obedece. C99 6. 5p2 :

  1. Entre el punto de secuencia anterior y el siguiente un objeto tendrá su valor almacenado modificado a lo sumo una vez por la evaluación de una expresión. [72] Además, el valor anterior se leerá únicamente para determinar el valor a conservar[73]

Con nota 73 aclarando aún más que

  1. Este párrafo renderiza expresiones de declaración indefinidas como

    i = ++i + 1;
    a[i++] = i;
    

    Mientras se permite

    i = i + 1;
    a[i] = i;
    

Los diversos puntos de secuencia se enumeran en el anexo C de C11 (y C99):

  1. Los siguientes son los puntos de secuencia descritos en 5.1.2.3:

    • , Entre las evaluaciones del designador de función y los argumentos reales en una llamada a función y la llamada real. (6.5.2.2).
    • Entre las evaluaciones del primer y segundo operandos de los siguientes operadores: lógico Y && (6.5.13); lógico O || (6.5.14); coma , (6.5.17).
    • Entre las evaluaciones del primer operando del condicional ? : operador y cualquiera de los operandos segundo y tercero es evaluado (6.5.15).
    • El final de un declarador completo: declaradores (6.7.6);
    • Entre la evaluación de una expresión completa y la siguiente expresión completa a evaluar. Las siguientes son expresiones completas: un inicializador que no es parte de un literal compuesto (6.7.9); la expresión en una sentencia expression (6.8.3); la expresión controladora de una sentencia selection (if o switch) (6.8.4); la expresión controladora de una sentencia while o do (6.8.5); cada una de las expresiones (opcionales) de una sentencia for (6.8.5.3); declaración de devolución (6.8.6.4).
    • Inmediatamente antes de que una función de biblioteca devuelva (7.1.4).
    • Después de las acciones asociadas con cada especificador de conversión de función de entrada/salida formateada (7.21.6, 7.29.2).
    • Inmediatamente antes e inmediatamente después de cada llamada a una función de comparación, y también entre cualquier llamada a una función de comparación y cualquier movimiento de los objetos pasados como argumentos a esa llamada (7.22.5).

La redacción de el mismo párrafo en C11 es:

  1. 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. Si hay múltiples orderings permitidos de las subexpresiones de una expresión, el comportamiento es indefinido si tal efecto secundario no secuenciado ocurre en cualquiera de los orderings.84)

Puede detectar tales errores en un programa, por ejemplo, usando una versión reciente de GCC con -Wall y -Werror, y luego GCC se negará a compilar su programa. La siguiente es la salida de gcc (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005:

% gcc plusplus.c -Wall -Werror -pedantic
plusplus.c: In function ‘main’:
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = i++ + ++i;
    ~~^~~~~~~~~~~
plusplus.c:6:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
plusplus.c:10:6: error: operation on ‘i’ may be undefined [-Werror=sequence-point]
    i = (i++);
    ~~^~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = u++ + ++u;
    ~~^~~~~~~~~~~
plusplus.c:14:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
plusplus.c:18:6: error: operation on ‘u’ may be undefined [-Werror=sequence-point]
    u = (u++);
    ~~^~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
    v = v++ + ++v;
    ~~^~~~~~~~~~~
plusplus.c:22:6: error: operation on ‘v’ may be undefined [-Werror=sequence-point]
cc1: all warnings being treated as errors

La parte importante es saber lo que una secuencia punto es ... y ¿qué es una secuencia y lo que no es. Por ejemplo, el operador de coma es un punto de secuencia, so

j = (i ++, ++ i);

Está bien definido, e incrementará i por uno, produciendo el valor antiguo, deseche ese valor; luego en el operador coma, resuelva los efectos secundarios; y luego incrementará i por uno, y el valor resultante se convierte en el valor de la expresión, es decir, esto es solo una forma artificial de escribir j = (i += 2) que es una vez más una forma "inteligente" de escribir

i += 2;
j = i;

Sin embargo, el , en las listas de argumentos de función es no un operador de coma, y no hay un punto de secuencia entre evaluaciones de argumentos distintos; en su lugar, sus evaluaciones no se secuencian entre sí; por lo que la función llamada

int i = 0;
printf("%d %d\n", i++, ++i, i);

Tiene un comportamiento indefinidoporque no hay un punto de secuencia entre las evaluaciones de i++ y ++i en los argumentos de la función, y el valor de i por lo tanto se modifica dos veces, tanto por i++ y ++i, entre el punto de secuencia anterior y el siguiente.

 10
Author: Antti Haapala,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-07 12:49:14

En https://stackoverflow.com/questions/29505280/incrementing-array-index-in-c alguien preguntó acerca de una declaración como:

int k[] = {0,1,2,3,4,5,6,7,8,9,10};
int i = 0;
int num;
num = k[++i+k[++i]] + k[++i];
printf("%d", num);

Que imprime 7... la OP esperaba que imprimiera 6.

Los incrementos ++i no están garantizados para completar todos antes del resto de los cálculos. De hecho, diferentes compiladores obtendrán diferentes resultados aquí. En el ejemplo que proporcionó, se ejecutaron los primeros 2 ++i, luego se leyeron los valores de k[], luego se leyó el último ++i k[].

num = k[i+1]+k[i+2] + k[i+3];
i += 3

Los compiladores modernos optimizarán esto muy bien. De hecho, posiblemente mejor que el código que escribiste originalmente (suponiendo que hubiera funcionado de la manera que esperabas).

 9
Author: TomOnTime,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:30

Una buena explicación sobre lo que sucede en este tipo de cálculo se proporciona en el documento n1188 de el sitio ISO W14.

Explico las ideas.

La regla principal de la norma ISO 9899 que se aplica en esta situación es 6.5p2.

Entre el punto de secuencia anterior y el siguiente un objeto tendrá su valor almacenado modificado a lo sumo una vez por la evaluación de una expresión. Además, el valor anterior se leerá únicamente a determinar el valor a almacenar.

Los puntos de secuencia en una expresión como i=i++ están antes de i= y después de i++.

En el documento que cité anteriormente se explica que se puede calcular el programa como si estuviera formado por pequeñas cajas, cada caja que contiene las instrucciones entre 2 puntos de secuencia consecutivos. Los puntos de secuencia se definen en el anexo C de la norma, en el caso de i=i++ hay 2 puntos de secuencia que delimitan una expresión completa. Tal la expresión es sintácticamente equivalente con una entrada de expression-statement en la forma Backus-Naur de la gramática (una gramática se proporciona en el anexo A de la Norma).

Así que el orden de las instrucciones dentro de una caja no tiene un orden claro.

i=i++

Puede interpretarse como

tmp = i
i=i+1
i = tmp

O como

tmp = i
i = tmp
i=i+1

Debido a que ambas formas para interpretar el código i=i++ son válidas y porque ambas generan respuestas diferentes, el comportamiento es indefinido.

Así que un punto de secuencia puede ser visto por el principio y el final de cada caja que compone el programa [las cajas son unidades atómicas en C] y dentro de una caja el orden de instrucciones no está definido en todos los casos. Cambiando ese orden uno puede cambiar el resultado a veces.

EDITAR:

Otra buena fuente para explicar tales ambigüedades son las entradas de c-faq sitio (también publicado como un libro) , a saber aquí y aquí y aquí .

 5
Author: alinsoar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-11-24 12:15:14

La razón es que el programa está ejecutando un comportamiento indefinido. El problema radica en el orden de evaluación, porque no se requieren puntos de secuencia según el estándar C++98 ( no se secuencian operaciones antes o después de otras según la terminología de C++11).

Sin embargo, si se apega a un compilador, encontrará el comportamiento persistente, siempre y cuando no agregue llamadas a funciones o punteros, lo que haría que el comportamiento sea más desordenado.

  • Así que primero GCC: Usando Nuwen MinGW 15 GCC 7.1 obtendrá:

    #include<stdio.h>
    int main(int argc, char ** argv)
    {
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 2
    
    i = 1;
    i = (i++);
    printf("%d\n", i); //1
    
    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 2
    
    u = 1;
    u = (u++);
    printf("%d\n", u); //1
    
    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); //2
    

    }

¿Cómo funciona el CCG? evalúa subexpresiones en un orden de izquierda a derecha para el lado derecho (RHS), luego asigna el valor al lado izquierdo (LHS). Así es exactamente como Java y C# se comportan y definen sus estándares. (Sí, el software equivalente en Java y C# tiene comportamientos definidos). Evalúa cada subexpresión una por una en la instrucción RHS en un orden de izquierda a derecha; para cada subexpresión: primero se evalúa el ++c (pre-incremento), luego se usa el valor c para la operación, luego el post incremento c++).

Según GCC C++: Operadores

En GCC C++, la precedencia de los operadores controla el orden en que los operadores individuales son evaluados

El código equivalente en el comportamiento definido C++ como GCC entiende:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    //i = i++ + ++i;
    int r;
    r=i;
    i++;
    ++i;
    r+=i;
    i=r;
    printf("%d\n", i); // 2

    i = 1;
    //i = (i++);
    r=i;
    i++;
    i=r;
    printf("%d\n", i); // 1

    volatile int u = 0;
    //u = u++ + ++u;
    r=u;
    u++;
    ++u;
    r+=u;
    u=r;
    printf("%d\n", u); // 2

    u = 1;
    //u = (u++);
    r=u;
    u++;
    u=r;
    printf("%d\n", u); // 1

    register int v = 0;
    //v = v++ + ++v;
    r=v;
    v++;
    ++v;
    r+=v;
    v=r;
    printf("%d\n", v); //2
}

Luego vamos a Visual Studio. Visual Studio 2015, usted obtener:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int i = 0;
    i = i++ + ++i;
    printf("%d\n", i); // 3

    i = 1;
    i = (i++);
    printf("%d\n", i); // 2 

    volatile int u = 0;
    u = u++ + ++u;
    printf("%d\n", u); // 3

    u = 1;
    u = (u++);
    printf("%d\n", u); // 2 

    register int v = 0;
    v = v++ + ++v;
    printf("%d\n", v); // 3 
}

Cómo funciona visual studio, toma otro enfoque, evalúa todas las expresiones pre-incrementos en la primera pasada, luego usa valores de variables en las operaciones en la segunda pasada, asigna de RHS a LHS en la tercera pasada, luego en la última pasada evalúa todas las expresiones post-incremento en una pasada.

Así que el equivalente en comportamiento definido C++ como Visual C++ entiende:

#include<stdio.h>
int main(int argc, char ** argv)
{
    int r;
    int i = 0;
    //i = i++ + ++i;
    ++i;
    r = i + i;
    i = r;
    i++;
    printf("%d\n", i); // 3

    i = 1;
    //i = (i++);
    r = i;
    i = r;
    i++;
    printf("%d\n", i); // 2 

    volatile int u = 0;
    //u = u++ + ++u;
    ++u;
    r = u + u;
    u = r;
    u++;
    printf("%d\n", u); // 3

    u = 1;
    //u = (u++);
    r = u;
    u = r;
    u++;
    printf("%d\n", u); // 2 

    register int v = 0;
    //v = v++ + ++v;
    ++v;
    r = v + v;
    v = r;
    v++;
    printf("%d\n", v); // 3 
}

Como indica la documentación de Visual Studio en Precedencia y Orden de Evaluación:

Cuando varios operadores aparecen juntos, tienen igual precedencia y se evalúan de acuerdo con su asociatividad. Los operadores de la tabla se describen en las secciones que comienzan con Operadores Postfix.

 3
Author: Muhammad Annaqeeb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-11 01:17:46

Su pregunta probablemente no fue, "¿Por qué estos constructos son un comportamiento indefinido en C?". Su pregunta fue probablemente, "¿Por qué este código (usando ++) no me dio el valor que esperaba?", y alguien marcó tu pregunta como un duplicado, y te envió aquí.

Esta respuesta intenta responder a esa pregunta: por qué su código no le dio la respuesta que esperaba, y cómo puede aprender a reconocer (y evitar) expresiones que no funcionarán como se esperaba.

Asumo ya has escuchado la definición básica de los operadores C ++ y --, y cómo la forma prefijo ++x difiere de la forma postfijo x++. Pero estos operadores son difíciles de pensar, así que para asegurarse de que entendió, tal vez escribió un pequeño programa de prueba que implica algo como

int x = 5;
printf("%d %d %d\n", x, ++x, x++);

Pero, para su sorpresa, este programa hizo no ayudarle a entender printed imprimió una salida extraña, inesperada, inexplicable, lo que sugiere que tal vez ++ hace algo completamente diferente, no es lo que pensabas que era.

O, tal vez estás viendo una expresión difícil de entender como{[38]]}

int x = 5;
x = x++ + ++x;
printf("%d\n", x);

Tal vez alguien te dio ese código como un rompecabezas. Este código tampoco tiene sentido, especialmente si se ejecuta it y si se compila y se ejecuta bajo dos compiladores diferentes, es probable que obtenga dos respuestas diferentes! Qué pasa con eso? ¿Qué respuesta es correcta? (Y la respuesta es que ambos lo son, o ninguno de ellos ser.)

Como ya has oído, todas estas expresiones son indefinidas, lo que significa que el lenguaje C no garantiza lo que harán. Este es un resultado extraño y sorprendente, porque probablemente pensó que cualquier programa que pudiera escribir, siempre y cuando compilara y ejecutara, generaría una salida única y bien definida. Pero en el caso del comportamiento indefinido, no es así.

¿Qué hace que una expresión sea indefinida? Son expresiones que implican ++ y -- siempre indefinido? Por supuesto que no: estos son operadores útiles, y si los usas correctamente, están perfectamente definidos.

Para las expresiones de las que estamos hablando lo que las hace indefinidas es cuando hay demasiado sucediendo a la vez, cuando no estamos seguros de en qué orden sucederán las cosas, pero cuando el orden importa para el resultado obtenemos.

Volvamos a los dos ejemplos que he usado en esta respuesta. Cuando escribí

printf("%d %d %d\n", x, ++x, x++);

La pregunta es, antes de llamar printf, ¿el compilador calcula primero el valor de x, o x++, o tal vez ++x? Pero resulta que no sabemos. No hay ninguna regla en C que diga que los argumentos de una función se evalúan de izquierda a derecha, o de derecha a izquierda, o en algún otro orden. Así que no podemos decir si el compilador hará x primero, luego ++x, luego x++, o x++ luego ++x luego x, o algún otro orden. Pero el orden claramente importa, porque dependiendo del orden que use el compilador, claramente obtendremos diferentes resultados impresos por printf.

¿Qué hay de esta expresión loca?

x = x++ + ++x;

El problema con esta expresión es que contiene tres diferentes intentos para modificar el valor de x: (1) x++ parte intenta agregar 1 a x, almacenar el nuevo valor en x, y devolver el valor de x; (2) ++x parte intenta agregar 1 a x, almacenar el nuevo valor en x, y devolver el nuevo valor de x; y (3) x = parte intenta asignar la suma de los otros dos de vuelta a x. ¿Cuál de esos tres intentos de asignación "ganará"? ¿Cuál de los tres valores se asignará realmente a x? Una vez más, y tal vez sorprendentemente, no hay ninguna regla en C para decirnos.

Usted podría imaginar que la precedencia o la asociatividad o la evaluación de izquierda a derecha le dice en qué orden suceden las cosas, pero no lo hacen. Puede que no me crean, pero por favor tomen mi palabra, y lo diré de nuevo: la precedencia y la asociatividad no determinan todos los aspectos de el orden de evaluación de una expresión en C. En particular, si dentro de una expresión hay múltiples puntos diferentes donde tratamos de asignar un nuevo valor a algo como x, la precedencia y la asociatividad no nos dicen cuál de esos intentos sucede primero, o último, o cualquier cosa.


Así que con todo ese trasfondo e introducción fuera del camino, si desea asegurarse de que todos sus programas estén bien definidos, qué expresiones puede escribir y cuáles pueden ¿no escribes?

Todas estas expresiones están bien: {[38]]}

y = x++;
z = x++ + y++;
x = x + 1;
x = a[i++];
x = a[i++] + b[j++];
x[i++] = a[j++] + b[k++];
x = *p++;
x = *p++ + *q++;

Estas expresiones son todas indefinidas: {[38]]}

x = x++;
x = x++ + ++x;
y = x + x++;
a[i] = i++;
a[i++] = i;
printf("%d %d %d\n", x, ++x, x++);

Y la última pregunta es, ¿cómo se puede saber qué expresiones están bien definidas y qué expresiones no están definidas?

Como he dicho antes, las expresiones indefinidas son aquellas en las que hay demasiadas cosas a la vez, en las que no puedes estar seguro de en qué orden suceden las cosas y dónde importa el orden:{[38]]}

  1. Si hay una variable que es al ser modificado (asignado a) en dos o más lugares diferentes, ¿cómo sabes qué modificación ocurre primero?
  2. Si hay una variable que está siendo modificada en un lugar, y tiene su valor utilizado en otro lugar, ¿cómo sabes si utiliza el valor antiguo o el nuevo valor?

Como ejemplo de #1, en la expresión

x = x++ + ++x;

Hay tres intentos de modificar `x. {[38]]}

Como ejemplo de #2, en la expresión

y = x + x++;

Ambos usamos el valor de x, y modificarlo.

Así que esa es la respuesta: asegúrese de que en cualquier expresión que escriba, cada variable se modifique como máximo una vez, y si se modifica una variable, no intente también usar el valor de esa variable en otro lugar.

 2
Author: Steve Summit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-16 11:54:35