Pregunta

Posible duplicado:
Comportamiento del operador de incremento previo y posterior en C, C++, Java y C#

Aquí hay un caso de prueba:


void foo(int i, int j)
{
   printf("%d %d", i, j);
}
...
test = 0;
foo(test++, test);

Esperaría obtener una salida "0 1", pero obtengo "0 0" ¿Qué da?

¿Fue útil?

Solución

Este es un ejemplo de comportamiento no especificado.El estándar lo hace no decir en qué orden se deben evaluar los argumentos.Esta es una decisión de implementación del compilador.El compilador es libre de evaluar los argumentos de la función en cualquier orden.

En este caso, parece que en realidad procesa los argumentos de derecha a izquierda en lugar del esperado de izquierda a derecha.

En general, generar efectos secundarios en los argumentos es una mala práctica de programación.

En lugar de foo(prueba++, prueba); Deberías escribir foo(prueba, prueba+1);prueba++;

Sería semánticamente equivalente a lo que estás intentando lograr.

Editar:Como Anthony señala correctamente, no está definido leer y modificar una sola variable sin un punto de secuencia intermedio.Entonces, en este caso, el comportamiento es de hecho indefinido.Entonces el compilador es libre de generar el código que quiera.

Otros consejos

Esto no es solo no especificado comportamiento, en realidad es comportamiento indefinido .

Sí, el orden de evaluación de los argumentos es no especificado, pero es indefinido leer y modificar una sola variable sin un punto de secuencia intermedio a menos que la lectura sea únicamente con el propósito de calcular el nuevo valor.No hay un punto de secuencia entre las evaluaciones de los argumentos de la función, por lo que f(test,test++) es comportamiento indefinido: test se lee para un argumento y se modifica para el otro.Si mueves la modificación a una función, entonces estás bien:

int preincrement(int* p)
{
    return ++(*p);
}

int test;
printf("%d %d\n",preincrement(&test),test);

Esto se debe a que hay un punto de secuencia en la entrada y salida de preincrement, por lo que la llamada debe evaluarse antes o después de la lectura simple.Ahora el orden es solo no especificado.

Tenga en cuenta también que la coma operador proporciona un punto de secuencia, por lo que

int dummy;
dummy=test++,test;

está bien --- el incremento ocurre antes de la lectura, entonces dummy se establece en el nuevo valor.

¡Todo lo que dije originalmente está MAL!El momento en el que se calcula el efecto secundario. es no especificado.Visual C++ realizará el incremento después de la llamada a foo() si test es una variable local, pero si test se declara como estática o global, se incrementará antes de la llamada a foo() y producirá resultados diferentes, aunque el valor final de la prueba será correcta.

El incremento realmente debería realizarse en una declaración separada después de la llamada a foo().Incluso si el comportamiento se especificara en el estándar C/C++, sería confuso.Se podría pensar que los compiladores de C++ señalarían esto como un error potencial.

Aquí es una buena descripción de puntos de secuencia y comportamiento no especificado.

<----INICIO DE MAL MAL MAL---->

El bit "++" de "test++" se ejecuta después de la llamada a foo.Entonces pasas (0,0) a foo, no (1,0)

Aquí está el resultado del ensamblador de Visual Studio 2002:

mov ecx, DWORD PTR _i$[ebp]
push    ecx
mov edx, DWORD PTR tv66[ebp]
push    edx
call    _foo
add esp, 8
mov eax, DWORD PTR _i$[ebp]
add eax, 1
mov DWORD PTR _i$[ebp], eax

El incremento se realiza DESPUÉS de la llamada a foo().Si bien este comportamiento es intencionado, ciertamente resulta confuso para el lector ocasional y probablemente debería evitarse.El incremento realmente debería realizarse en una declaración separada después de la llamada a foo()

<----FIN DE MAL MAL MAL ---->

Es un "comportamiento no especificado", pero en la práctica, con la forma en que se especifica la pila de llamadas de C, casi siempre garantiza que lo verá como 0, 0 y nunca como 1, 0.

Como alguien señaló, la salida del ensamblador de VC inserta primero el parámetro más a la derecha en la pila.Así es como se implementan las llamadas a funciones C en ensamblador.Esto es para acomodar la característica de "lista interminable de parámetros" de C.Al presionar los parámetros en orden de derecha a izquierda, se garantiza que el primer parámetro será el elemento superior de la pila.

Tome la firma de printf:

int printf(const char *format, ...);

Esas elipses denotan un número desconocido de parámetros.Si los parámetros se empujaran de izquierda a derecha, el formato estaría en la parte inferior de una pila cuyo tamaño no conocemos.

Sabiendo que en C (y C++) los parámetros se procesan de izquierda a derecha, podemos determinar la forma más sencilla de analizar e interpretar una llamada a función.Llegue al final de la lista de parámetros y comience a presionar, evaluando cualquier declaración compleja a medida que avanza.

Sin embargo, ni siquiera esto puede salvarle, ya que la mayoría de los compiladores de C tienen una opción para analizar funciones "estilo Pascal".Y todo esto significa que los parámetros de la función se insertan en la pila de izquierda a derecha.Si, por ejemplo, printf se compiló con la opción Pascal, entonces la salida probablemente sería 1, 0 (sin embargo, dado que printf usa la elipse, no creo que se pueda compilar al estilo Pascal).

C no garantiza el orden de evaluación de los parámetros en una llamada a función, por lo que con esto podrías obtener los resultados "0 1" o "0 0".El orden puede cambiar de un compilador a otro, y el mismo compilador podría elegir diferentes órdenes según los parámetros de optimización.

Es más seguro escribir foo(test, test + 1) y luego hacer ++test en la siguiente línea.De todos modos, el compilador debería optimizarlo si es posible.

Es posible que el compilador no esté evaluando los argumentos en el orden esperado.

El orden de evaluación de los argumentos de una función no está definido.En este caso parece que los hizo de derecha a izquierda.

(La modificación de variables entre puntos de secuencia básicamente permite que un compilador haga lo que quiera).

Um, ahora que el OP ha sido editado para mantener la coherencia, no está sincronizado con las respuestas.La respuesta fundamental sobre el orden de evaluación es correcta.Sin embargo, los valores posibles específicos son diferentes para foo(++test, test);caso.

++prueba voluntad se incrementará antes de pasarse, por lo que el primer argumento siempre será 1.El segundo argumento será 0 o 1 según el orden de evaluación.

Según el estándar C, es un comportamiento indefinido tener más de una referencia a una variable en un solo punto de secuencia (aquí puede pensar en eso como una declaración o parámetros de una función) donde una o más de esas referencias incluye una modificación pre/post.Entonces:foo(f++,f) <--indefinido en cuanto a cuándo se incrementa f.Y de la misma manera (veo esto todo el tiempo en el código de usuario):*p = p++ + p;

Normalmente, un compilador no cambiará su comportamiento para este tipo de cosas (excepto en el caso de revisiones importantes).

Evítelo activando las advertencias y prestándoles atención.

Para repetir lo que otros han dicho, este no es un comportamiento no especificado, sino más bien indefinido.Este programa puede generar legalmente cualquier cosa o nada, dejar n con cualquier valor o enviar correos electrónicos insultantes a su jefe.

Como cuestión de práctica, los escritores de compiladores generalmente harán lo que les resulte más fácil de escribir, lo que generalmente significa que el programa buscará n una o dos veces, llamará a la función y la incrementará en algún momento.Esto, como cualquier otro comportamiento imaginable, está bien según el estándar.No hay razón para esperar el mismo comportamiento entre compiladores, versiones o con diferentes opciones de compilador.No hay ninguna razón por la que dos ejemplos diferentes pero de apariencia similar en el mismo programa deban compilarse de manera consistente, aunque apuesto que esa es la forma.

En resumen, no hagas esto.Pruébelo en diferentes circunstancias si tiene curiosidad, pero no pretenda que haya un único resultado correcto o incluso predecible.

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