2008-09-19 7 views
15

Duplicar posibles:
Pre & post increment operator behavior in C, C++, Java, & C#incremento del anuncio

Aquí es un caso de prueba:


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

que se puede esperar para obtener una salida "0 1", pero obtengo "0 0" ¿Qué le da?

+0

¿Quizás su descripción/ejemplo debería incluir la pregunta del título? –

+0

El título y el ejemplo del código no coinciden – itj

+0

La pregunta se confunde entre el título y el ejemplo del código. el título tiene ++ n el ejemplo tiene la prueba ++ – itj

Respuesta

47

Este es un ejemplo de comportamiento no especificado. El estándar no indica en qué orden deben evaluarse los argumentos. Esta es una decisión de implementación del compilador. El compilador puede 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 de los esperados de izquierda a derecha.

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

En lugar de foo (prueba ++, prueba); debe escribir foo (prueba, prueba + 1); prueba ++;

Sería semánticamente equivalente a lo que está tratando de lograr.

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

+0

Como énfasis agregado, para evitar este tipo de problemas siempre tengo incrementos como una declaración separada. –

+0

No quisiera enfatizar eso. Tal vez si eres nuevo en el idioma, es posible que quieras tomarlo con calma, pero esta sintaxis existe por una razón, y no es solo porque i + = 1 es 3 caracteres más. –

+0

¿Es solo yo o el orden de evaluación para operandos no tiene nada que ver con lo que ves?test ++ es * incremento * post *, por lo que la prueba se incrementa * después * de aprobación, como dice Mike Thompson a continuación. – agnul

1

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

14

¡Todo lo que dije originalmente es INCORRECTO! El punto en el tiempo en el que se calcula el efecto secundario es no especificado. Visual C++ realizará el incremento después de la llamada a foo() si la prueba es una variable local, pero si la prueba 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 debe hacerse 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 C++ marcarían esto como un posible error.

Here es una buena descripción de los puntos de secuencia y el comportamiento no especificado.

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

El "++" poco de "prueba ++" es ejecutado después de la llamada a foo. Así se pasa en (0,0) a foo, no (1,0)

Aquí está la salida del ensamblador desde 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 por diseño, es ciertamente confuso para el lector casual y probablemente debería evitarse. El incremento realmente debe hacerse en una declaración separada después de la llamada a foo()

< ---- FIN DEL MAL MAL MAL ---->

+0

No, no es así, pero no puede ver eso en este ejemplo porque la prueba no es global y VS2002 evaluó primero el último argumento (legal y algo lógico para una pila) – MSalters

1

el orden de evaluación de argumentos a una función es indefinido En este caso, parece que los hizo de derecha a izquierda.

(Modificación de las variables entre los puntos de secuencia, básicamente, permite a un compilador para hacer lo que quiera.)

2

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

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

6

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

Como alguien ha señalado, el la salida del ensamblador por VC empuja primero el parámetro más a la derecha en la pila. Así es como se implementan las llamadas de función C en ensamblador. Esto es para acomodar la característica de "lista de parámetros sin fin" de C. Al presionar los parámetros en un orden de derecha a izquierda, se garantiza que el primer parámetro será el elemento superior en la pila. firma

La Opinión de printf:

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

Esos puntos suspensivos denotan un número indeterminado de parámetros. Si los parámetros se presionaron de izquierda a derecha, el formato estaría en la parte inferior de una pila de la cual no conocemos el tamaño.

Sabiendo que en C (y C++) que 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, incluso esto no puede salvarte ya que la mayoría de los compiladores de C tienen una opción para analizar las 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).

29

Esto no es sólo sin especificar el comportamiento, en realidad es un comportamiento indefinido .

Sí, el orden de evaluación argumento es no especificado, pero es indefinido a leer y modificar una sola variable sin un punto de secuencia intermedia a menos que la lectura es el único fin de calcular el nuevo valor. No hay 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 mueve la modificación en una función entonces está bien:

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

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

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

también que la coma operador proporciona un punto de secuencia Nota, por lo

int dummy; 
dummy=test++,test; 

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

+0

Para la prosperidad, creo que vale la pena agregar la referencia estándar para estos. ISO C++ 5p4. –

+5

¿No te refieres a la posteridad? ¿O quiere ayudar a que los programadores sean más ricos? –

+1

@Anthony, creo que te perdiste los parens alrededor del inicializador de 'dummy' :) –

1

Um, ahora que el OP ha sido editado por 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 el foo (prueba de ++, prueba); caso.

++ prueba será ser incrementado antes de ser pasado, por lo que el primer argumento siempre será 1. El segundo argumento será 0, o 1 dependiendo de orden de evaluación.

1

De acuerdo con el estándar C, es un comportamiento indefinido tener más de una referencia a una variable en un único punto de secuencia (aquí se puede considerar como una declaración o parámetros de una función) donde uno o más de esas referencias incluye una modificación pre/post. Entonces: foo (f ++, f) < --infinito en cuanto a cuando f se incrementa. Y del mismo modo (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 para revisiones importantes).

Evítalo activando las advertencias y prestando atención a ellas.

1

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

Como práctica, los escritores de compiladores generalmente hacen lo que es más fácil para ellos escribir, lo que generalmente significa que el programa buscará n una o dos veces, llamará a la función e incrementará en algún momento. Esto, como cualquier otro comportamiento concebible, está bien de acuerdo con el estándar. No hay ninguna razón para esperar el mismo comportamiento entre compiladores o versiones, o con diferentes opciones de compilación. No hay ninguna razón para que dos ejemplos diferentes pero similares en el mismo programa tengan que compilarse de manera consistente, aunque así es como apostaría.

En resumen, no hagas esto. Pruébelo bajo diferentes circunstancias si tiene curiosidad, pero no pretenda que hay un solo resultado correcto o incluso predecible.

Cuestiones relacionadas