Pregunta

#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\n", ++w, w); // shouldn't this print 1 1

   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?
}
¿Fue útil?

Solución

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

Por lo que yo sé, la norma no dice explícitamente ¿Por qué existe el concepto de comportamiento indefinido. En mi mente, es simplemente porque los diseñadores del lenguaje quería que haya un margen de maniobra en la semántica, en lugar de decir, que requieren que todas las implementaciones manejan desbordamiento de enteros en la misma forma, lo que es muy probable imponer costos graves de rendimiento, que acaba de dejar el comportamiento no definido de manera que si se escribe código que provoca desbordamiento de enteros, cualquier cosa puede pasar.

Entonces, con esto en mente, ¿por qué son estos "problemas"? La lengua dice claramente que ciertas cosas conducen a comportamiento indefinido . No hay ningún problema, no hay un "debe" en cuestión. Si el comportamiento no definido cambia cuando una de las variables involucradas se declara volatile, eso no prueba ni cambia nada. Es Indefinido ; no se puede razonar sobre el comportamiento.

Su ejemplo más interesante, buscando, el que tiene

u = (u++);

es un ejemplo de libro de texto de un comportamiento indefinido (véase la entrada de Wikipedia sobre href="http://en.wikipedia.org/wiki/Sequence_point" señala ).

Otros consejos

Sólo compilar y desmonte su línea de código, si así lo quieren saber cómo es exactamente lo que se obtiene lo que está recibiendo.

Esto es lo que me pasa 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 ... supongo que la instrucción 0x00000014 era una especie de optimización del compilador?)

Creo que las partes pertinentes de la norma C99 están Expresiones 6.5, § 2

  

Entre el anterior y el siguiente punto de la secuencia de un objeto tendrá su valor almacenado   modificado como máximo una vez por la evaluación de una expresión. Además, el valor previo   será de sólo lectura para determinar el valor a almacenar.

y 6.5.16 Los operadores de asignación, § 4:

  

El orden de evaluación de los operandos está especificado. Si se hace un intento de modificar   el resultado de un operador de asignación o para acceder a ella después de que el siguiente punto de la secuencia, la   comportamiento no está definido.

El comportamiento en realidad no puede ser explicado porque invoca tanto comportamiento no especificado y comportamiento indefinido, por lo que no podemos hacer predicciones generales sobre este código, aunque si se lee Olve Maudal de trabajo, tales como profundo C y sin especificar y Indefinido a veces se pueden hacer buenas conjeturas en casos muy específicos con un compilador y entorno específico pero por favor no hacer eso en cualquier parte cerca de la producción.

Así que de pasar a comportamiento no especificado , en proyecto de norma C99 párrafo section6.5 3 dice ( énfasis mío ):

  

La agrupación de operadores y operandos se indica por el syntax.74) Excepto como se especifica   posterior (para la función de llamada (), &&, ||,:?, y los operadores por comas), el orden de evaluación de subexpresiones y el orden en el que los efectos secundarios tienen lugar son tanto no especificado <. / p>

Así que cuando tenemos una línea como la siguiente:

i = i++ + ++i;

no sabemos si i++ o ++i serán evaluados por primera vez. Esto se debe principalmente a dar a los mejores opciones para la optimización .

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

  

Entre el anterior y el siguiente punto de la secuencia de un objeto tendrá su valor almacenado   modificado como máximo una vez por la evaluación de una expresión. Además, el valor previo   será de sólo lectura para determinar el valor a almacenar .

que cita los siguientes ejemplos de código como está sin definir:

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

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

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

i = (i++);
^    ^

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

u = (u++);
^    ^

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

comportamiento no especificado se define en el proyecto de norma C99 en la sección 3.4.4 como:

  

El uso de un valor especificado, o cualquier otro comportamiento en esta Norma Internacional proporciona   dos o más posibilidades y no impone requisitos adicionales en que se elige en cualquier   instancia

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

  

comportamiento, en el uso de una construcción de programa no portátil o errónea o de datos erróneos,   para los que esta norma no impone requisitos

y señala que:

  

Posible comportamiento indefinido varía de ignorar la situación completamente con resultados imprevisibles, a comportarse durante la traducción o la ejecución del programa de una manera característica documentado del medio ambiente (con o sin la emisión de un mensaje de diagnóstico), para terminar una traducción o ejecución ( con la emisión de un mensaje de diagnóstico).

La mayoría de las respuestas aquí tomadas de C estándar haciendo hincapié en que el comportamiento de estas construcciones no están definidos. Para entender por qué el comportamiento de estas construcciones no están definidos , vamos a entender estos términos por primera vez en la luz de la norma C11:

Secuenciado: (5.1.2.3)

  

Dado cualquier A dos evaluaciones y B, si A se secuencia antes B, a continuación, la ejecución de A será anterior a la ejecución de B.

unsequenced:

  

Si A no se secuencia antes o después de B, entonces A y B son unsequenced.

Las evaluaciones pueden ser una de dos cosas:

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

secuencia de puntos:

  

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

Ahora que se acerca a la pregunta, por las expresiones como

int i = 1;
i = i++;

norma dice que:

6,5 expresiones:

  

Si un efecto secundario en un objeto de escalar es unsequenced relación a un efecto secundario diferente en el mismo objeto escalar o un cálculo de valor usando el valor de la misma escalar objeto, el comportamiento no está definido . [...]

Por lo tanto, la expresión anterior invoca UB porque dos efectos secundarios sobre el mismo i objeto es unsequenced respecto a la otra. Esto significa que no se secuencia si el efecto secundario por la asignación a i se llevará a cabo antes o después de que el efecto secundario por ++.
Dependiendo de si la asignación se produce antes o después de la ampliación, los diferentes resultados serán producidos y eso es la del caso de comportamiento indefinido .

Permite cambiar el nombre del i a la izquierda de la asignación se il y a la derecha de la asignación (en el i++ expresión) se ir, entonces la expresión sea 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 operador ++ Postfix es que:

  

sólo porque el ++ viene después de la variable no significa que el incremento ocurre finales . El incremento puede ocurrir tan pronto como los gustos del compilador , siempre y cuando el compilador garantiza que el valor original se utiliza .

Esto significa que el il = ir++ expresión podría ser evaluada ya sea 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 misiones y ++ y por lo tanto invoca UB.

Otra manera de responder a esta, en lugar de enredarse en detalles arcanos de puntos de secuencia y un comportamiento indefinido, es simplemente pedir, lo que se supone que quiere decir? ¿Cuál fue el programador tratando de hacer?

El primer fragmento se le preguntó acerca, i = i++ + ++i, es bastante claramente una locura en mi libro. Nadie podría escribirlo en un programa real, no es obvio lo que hace, no hay ningún algoritmo concebible que alguien podría haber estado tratando de código que habría dado lugar a esta secuencia artificial particular de operaciones. Y ya que no es obvio para usted y para mí lo que tiene que hacer, está bien en mi libro si el compilador no puede averiguar lo que tiene que 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 a i. Pero hay un par de maneras de hacer esto en C. La forma más sencilla de añadir 1 a i, y asignar el resultado a i, es el mismo en casi cualquier lenguaje de programación:

i = i + 1

C, por supuesto, tiene un acceso directo a mano:

i++

Esto significa, "añadir 1 a i, y asignar el resultado 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 a i, y asignar el resultado a i". Estamos confundidos, así que no me molesta demasiado si el compilador se confunde, también.

Siendo realistas, la única vez que estas expresiones se escriben locas es cuando la gente los están usando como ejemplos artificiales de cómo se supone ++ para trabajar. Y, por supuesto, es importante entender cómo funciona ++. Sin embargo, una regla práctica para el uso ++ es: "Si no es obvio lo que una expresión utilizando medios ++, no lo escriba."

Se utilizó para pasar horas y horas en comp.lang.c discutir expresiones como estas y ¿Por qué que están sin definir. Dos de mis respuestas más largas, que tratan de explicar realmente por qué, se archivan en la web:

cuestionar 3,8 y el resto de las preguntas en sección 3 de la C lista de preguntas frecuentes.

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.

Si bien esto también es comportamiento indefinido Como ya se dijo, existen 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 no especificado.Es decir, expresiones i++ y ++i podría evaluarse en cualquier orden. estándar C11 tiene algunas descripciones relevantes sobre esto:

Anexo J, conductas no especificadas

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

3.4.4, comportamiento no especificado

El uso de un valor no especificado u otro comportamiento donde este estándar internacional proporciona dos o más posibilidades y no impone requisitos adicionales sobre los que se elige en cualquier caso.

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

El comportamiento no especificado En sí mismo 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 está especificado.Pero es una declaración perfectamente legal y válida.hay No Comportamiento indefinido en esta declaración.Porque las modificaciones (++x y y++) están hechos para distinto objetos.

¿Qué representa la siguiente declaración?

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

como comportamiento indefinido es el hecho de que estas dos expresiones modifican la mismo objeto i sin intervenir punto de secuencia.


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

Esta es una distinción importante porque la operador de coma introduce un punto de secuencia entre la evaluación de sus operandos, lo que hace legal lo siguiente:

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 de coma evalúa sus operandos de izquierda a derecha y produce solo el valor del último operando.Entonces en j = (++i, i++);, ++i incrementos i a 6 y i++ produce el antiguo valor de i (6) que está asignado a j.Entonces i se convierte 7 debido al post-incremento.

Entonces si el 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 el coma aquí hay un separador.


Para aquellos que son nuevos en comportamiento indefinido se beneficiaría de la lectura Lo que todo programador de C debe saber sobre el comportamiento indefinido para comprender el concepto y muchas otras variantes de comportamiento indefinido en C.

Esta publicación: Comportamiento indefinido, no especificado y definido por la implementación también es relevante.

Si bien es poco probable que los compiladores y procesadores serían realmente lo hacen, sería legal, bajo el estándar C, para el compilador de 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

Si bien no creo que ningún procesadores son compatibles con el hardware para permitir que tal cosa se haga de manera eficiente, es fácil imaginar situaciones en las que tal comportamiento podría hacer que el código multi-hilo más fácil (por ejemplo, que garantizaría que si dos subprocesos intentan realizar la secuencia anterior al mismo tiempo, i conseguiría incrementado en dos) y no es totalmente inconcebible que algunos procesadores futuro podría proporcionar una característica algo por el estilo.

Si el compilador fuera a escribir i++ como se indicó anteriormente (jurídica en virtud de la norma) y estaban a intercalar las instrucciones anteriores a lo largo de la evaluación de la expresión general (también legal), y si no sucede a notar que una de las otras instrucciones ocurrido acceder a i, sería posible (y legal) para el compilador para generar una secuencia de instrucciones que se bloqueaba. Sin duda, un compilador es casi seguro que detecta el problema en el caso en que la misma i variable se utiliza en ambos lugares, pero si una rutina acepta referencias a dos punteros p y q, y utiliza (*p) y (*q) en la expresión anterior (en lugar que el uso de i dos veces) no sería necesario que el compilador de reconocer o evitar el estancamiento que se produciría si la dirección del mismo objeto se pasaron tanto para p y q.

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

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

y así sucesivamente violar esa regla. La norma también dice que el comportamiento es indefinido y no especificada. Algunos compiladores no detectan estos y producen algún resultado pero esto no es por norma.

Sin embargo, dos variables diferentes se pueden incrementar entre dos puntos de secuencia.

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

Lo anterior es una práctica común de codificación mientras copia / análisis de cadenas.

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

  1. Entre el punto de secuencia anterior y el siguiente, el valor almacenado de un objeto deberá modificarse como máximo una vez mediante la evaluación de una expresión.[72] Además, el valor anterior se leerá únicamente para determinar el valor que se almacenará [73]

Con nota al pie 73 aclarando aún más que

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

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

    mientras 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 operando de los siguientes operadores:AND lógico && (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 que se evalúe (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 forma parte de un literal compuesto (6.7.9);la expresión en una declaración de expresión (6.8.3);la expresión de control de una declaración de selección (if o switch) (6.8.4);la expresión controladora de una declaración while o do (6.8.5);cada una de las expresiones (opcionales) de una declaración for (6.8.5.3);la expresión (opcional) en una declaración de devolución (6.8.6.4).
    • Inmediatamente antes de que regrese una función de biblioteca (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 para esa llamada (7.22.5).

La redacción del 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 no está definido.Si hay múltiples ordenamientos permitidos de las subexpresiones de una expresión, el comportamiento no está definido si dicho efecto secundario no secuenciado ocurre en cualquiera de los ordenamientos.84)

Puede detectar dichos errores en un programa, por ejemplo, utilizando una versión reciente de GCC con -Wall y -Werror, y luego GCC se negará rotundamente a compilar su programa.El siguiente es el resultado 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

Lo importante es saber qué es un punto de secuencia - y qué es un punto de secuencia y qué no es.Por ejemplo el operador de coma es un punto de secuencia, entonces

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

está bien definido y se incrementará i por uno, arrojando el valor anterior, descarta ese valor;luego, en el operador de 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 deciresta 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 funciones es no un operador de coma y no hay un punto de secuencia entre evaluaciones de distintos argumentos;en cambio, sus evaluaciones no están secuenciadas entre sí;entonces la llamada a la función

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

tiene comportamiento indefinido porque No existe un punto de secuencia entre las evaluaciones de i++ y ++i en argumentos de función, y el valor de i por lo tanto se modifica dos veces, por ambos i++ y ++i, entre el punto de secuencia anterior y el siguiente.

En https://stackoverflow.com/questions/29505280/incrementing-array-index- en-c alguien le 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 ... OP espera para imprimir 6.

Los incrementos ++i no se garantiza que todo completo antes de que el resto de los cálculos. De hecho, diferentes compiladores obtendrán diferentes resultados aquí. En el ejemplo que nos ha facilitado, los primeros 2 ++i ejecutada, a continuación, se leyeron los valores de k[], entonces el último ++i continuación k[].

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

compiladores modernos optimizarán esto muy bien. De hecho, posiblemente mejor que el código que escribió originalmente (suponiendo que había trabajado la forma en que había esperado).

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

Este Respuesta trata de responder a esta pregunta: ¿por qué el código no se dé la respuesta que esperaba, y cómo se puede aprender a reconocer (y evitar) las expresiones que no funcionan como se esperaba <. / p>

Asumo que has oído la definición básica de los operadores ++ y -- de C por ahora, y cómo la forma de prefijo ++x difiere de la forma de sufijo x++. Sin embargo, estos operadores son difíciles de pensar, por lo que para asegurarse de que entiende, tal vez usted escribió un minúsculo 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 ayuda a entender - se imprime una extraña, inesperada, inexplicable de salida, lo que sugiere que tal vez ++ hace algo completamente diferente, no en todo lo que pensabas lo hizo.

O, tal vez usted está buscando en un disco de entender la expresión como

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

Tal vez alguien le dio ese código como un rompecabezas. Este código también no tiene sentido, sobre todo si se ejecuta - y si se compila y ejecuta bajo dos diferentes compiladores, es muy probable que obtener dos respuestas diferentes! ¿Que pasa con eso? La respuesta correcta? (Y la respuesta es que ambos son, o ninguno de ellos son.)

Como se ha escuchado por ahora, todas estas expresiones son Indefinido , lo que significa que el lenguaje C no ofrece ninguna garantía sobre lo que van a hacer. Este es un resultado extraño y sorprendente, ya que probablemente pensó que cualquier programa que podría escribir, siempre y cuando se compila y se corrió, generaría una salida única y bien definida. Pero en el caso de un comportamiento indefinido, eso no es así.

Lo que hace una expresión indefinida? Son expresiones que implican ++ y -- siempre indefinido? Por supuesto que no:. Estos son los operadores útiles, y si se utilizan correctamente, son perfectamente bien definido

Para las expresiones que estamos hablando acerca de lo que los hace indefinido es cuando hay demasiadas cosas a la vez, cuando no estamos seguros de qué orden las cosas van a suceder, pero cuando los asuntos a fin de obtener el resultado que.

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 a printf, el compilador no calcular el valor de x primero, o x++, o tal vez ++x? Pero resulta que no sabemos . No hay ninguna regla en C que dice que los argumentos a una función se evalúan de izquierda a derecha o de derecha a izquierda, o de alguna otra orden. Así que no podemos decir si el compilador hará x primero, y luego ++x, entonces x++ o x++ continuación ++x continuación x, o algún otro fin. Pero el orden es claramente importante, ya que dependiendo de qué orden los usos del compilador, vamos a obtener resultados diferentes claramente impreso por printf.

¿Qué pasa con esta expresión loco?

x = x++ + ++x;

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

Es posible imaginar que precedencia o asociatividad o evaluación de izquierda a derecha te dice lo que sucedan cosas en orden, pero no lo hacen. Puede que no me creen, pero por favor tome mi palabra para ella, y voy a decirlo una vez más: precedencia y asociatividad no determinan todos los aspectos de la orden de evaluación de una expresión en C. En particular, si el plazo de una expresión hay múltiples diferentes puntos donde tratamos de asignar un nuevo valor a algo así como x, precedencia y asociatividad hacer no nos dicen cuál de esos intentos que ocurra primero, o el último, ni nada.


Así que con todo lo que el fondo y la introducción fuera del camino, si usted quiere asegurarse de que todos los programas están bien definidos, que las expresiones se puede escribir, y cuáles pueden no escribir?

Estas expresiones son todas bien:

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 están indefinidos:

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 puede saber qué expresiones están bien definidos y que las expresiones no están definidos?

Como he dicho antes, las expresiones no definidas son aquellos en los que hay demasiadas cosas a la vez, donde no se puede estar seguro de qué orden las cosas suceden en, y donde las cuestiones de orden:

  1. Si hay una variable que está siendo modificado (asignado a) en dos o más lugares diferentes, ¿cómo sabes lo que ocurra primero modificación?
  2. Si hay una variable que está siendo modificado en un solo lugar, y que tiene su valor utilizado en otro lugar, ¿cómo se sabe si se utiliza el valor anterior o el nuevo valor?

Como un ejemplo de # 1, en la expresión

x = x++ + ++x;

hay tres intentos de modificar `x.

Como un ejemplo de # 2, en la expresión

y = x + x++;

que tanto usamos el valor de x, y modificarlo.

Así que esa es la respuesta: asegurarse de que en cualquier expresión escrita, cada variable se modifica como máximo una vez, y si se modifica una variable, no se hará también intenta utilizar el valor de esa variable en otro lugar

Una buena explicación de lo que sucede en este tipo de cálculo se presenta en el documento de href="http://www.open-std.org/jtc1/sc22/wg14/www/docs/" rel="noreferrer"> 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 anterior y el siguiente punto de la secuencia de un objeto tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión. Además, el valor previa se de sólo lectura para determinar el valor a almacenar.

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

En el trabajo que he citado más arriba se explica que se puede averiguar el programa como formados por pequeñas cajas, cada caja contiene las instrucciones entre 2 puntos consecutivos de secuencia. 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 expresión es sintácticamente equivalente con una entrada de expression-statement en 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 ser interpretado como

tmp = i
i=i+1
i = tmp

o como

tmp = i
i = tmp
i=i+1

debido a que ambos todas estas formas de interpretar el código i=i++ son válidas y por tanto generar diferentes respuestas, el comportamiento es indefinido.

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

EDIT:

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

La razón es que el programa se está ejecutando un comportamiento indefinido. El problema reside en el orden de evaluación, porque no hay puntos secuencia requerida de acuerdo con C ++ 98 estándar (no hay operaciones se secuencia antes o después de otra de acuerdo con C ++ 11 terminología).

Sin embargo, si usted se pega a un compilador, se encuentra el comportamiento persistente, siempre y cuando no se agrega llamadas a funciones o punteros, lo que haría que el comportamiento más desordenado.

  • Así que primero el CCG: Utilizando 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 GCC? que evalúa las expresiones sub a una orden de izquierda a derecha para el lado derecho (RHS), a continuación, asigna el valor a la izquierda (LHS). Así es exactamente como Java y C # se comportan y definir sus normas. (Sí, el software equivalente en Java y C # ha definido comportamientos). Se evalúan cada sub expresión de uno en uno en la Declaración de RHS en un orden de izquierda a derecha; para cada expresión sub: la ++ c (pre-incremento) se evalúa primero entonces el valor de c se utiliza para la operación, entonces el poste incremento c ++)

.

según GCC C ++: Operadores

  

En GCC C ++, la precedencia de los operadores controla el orden en   el cual se evalúan los operadores individuales

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

#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
}

A continuación, vamos a Visual Studio . Visual Studio 2015, que se obtiene:

#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, se necesita otro enfoque, se evalúa todas las expresiones pre-incrementos en la primera pasada, a continuación, utiliza valores de las variables en las operaciones en el segundo pase, asignar de RHS a LHS en tercer pase, a continuación, en el último pase se evalúa todas las expresiones de incremento posterior en una sola pasada.

Así que el equivalente en comportamiento definido como C ++ 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 estados de documentación de Visual Studio en Precedencia y orden de evaluación :

  

Cuando varios operadores aparecen juntos, tienen la misma precedencia y son evaluados de acuerdo a su asociatividad. Los operadores de la tabla se describen en las secciones que comienzan con Operadores de sufijo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top