elhacker.net cabecera Bienvenido(a), Visitante. Por favor Ingresar o Registrarse
¿Perdiste tu email de activación?.

 

 


Tema destacado: Trabajando con las ramas de git (tercera parte)


+  Foro de elhacker.net
|-+  Programación
| |-+  Programación C/C++ (Moderadores: Eternal Idol, Littlehorse, K-YreX)
| | |-+  Operadores incremento/decremento en prefijo y postfijo.
0 Usuarios y 1 Visitante están viendo este tema.
Páginas: [1] 2 Ir Abajo Respuesta Imprimir
Autor Tema: Operadores incremento/decremento en prefijo y postfijo.  (Leído 6,407 veces)
K-YreX
Moderador
***
Desconectado Desconectado

Mensajes: 1.008



Ver Perfil
Operadores incremento/decremento en prefijo y postfijo.
« en: 2 Octubre 2019, 14:46 pm »

Ya sé que este suele ser un tema básico pero me he encontrado con un resultado inesperado en el funcionamiento de estos operadores.
Mi problema es que estaba implementando un algoritmo de ordenamiento por inserción tanto en C++ como en Java; sin embargo, el resultado obtenido en C++ no era el que esperaba por eso pongo la duda en este foro.
Aquí el código en Java:
Código
  1. public static void insertionSort(int[] numbers){
  2.    for(int i = 1; i < numbers.length; ++i){
  3.        int currentValue = numbers[i];
  4.        int previousIndex = i-1;
  5.        while(previousIndex >= 0 && currentValue < numbers[previousIndex])
  6.            numbers[previousIndex+1] = numbers[previousIndex--];
  7.        numbers[previousIndex+1] = currentValue;
  8.        showArray(numbers);
  9.    }
  10. }

Y aquí el código en C++:
Código
  1. void insertionSort(int *numbers, const int size){
  2.    for(size_t i = 1; i < size; ++i){
  3.        int currentValue = numbers[i];
  4.        int previousIndex = i-1;
  5.        while(previousIndex >= 0 && currentValue < numbers[previousIndex])
  6.            numbers[previousIndex+1] = numbers[previousIndex--];
  7.        numbers[previousIndex+1] = currentValue;
  8.        showArray(numbers, size);
  9.    }
  10. }
Como se puede ver, códigos idénticos. El problema está en la línea 6 del código en C++. He visto que había valores que se repetían y después de un rato investigando he visto que si en C++ se cambia la línea 6 por:
Código
  1. numbers[previousIndex+2] = numbers[previousIndex--];
Entonces sí funciona. Y si se cambia el bucle <while> por:
Código
  1. while(previousIndex >= 0 && currentValue < numbers[previousIndex]){
  2.    numbers[previousIndex+1] = numbers[previousIndex];
  3.    --previousIndex;
  4. }
También funciona correctamente. Entonces el problema sé que está en el orden en que se realiza el decremento. Me gustaría sabes cómo funciona eso en profundidad ya que veo que no funciona igual en Java que en C++.

PD: Agradecería también una explicación del funcionamiento en Java para ver cuáles son las diferencias exactamente. O si es mejor que abra otro tema en el foro de Java para esta parte me lo podéis decir también. :-X :-X


« Última modificación: 6 Octubre 2019, 02:53 am por YreX-DwX » En línea

Código
  1. cout << "Todos tenemos un defecto, un error en nuestro código" << endl;
engel lex
Moderador Global
***
Desconectado Desconectado

Mensajes: 15.514



Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #1 en: 2 Octubre 2019, 15:47 pm »

aqui hay un tema que se explicó con un poco de detalle el asunto relacionado a los incrementos/decrementos post/pre, esto es un problema comun a descubrir cuando se está aprendiendo

https://foro.elhacker.net/buscador-t483138.0.html

no es un asunto de java vs c++ es un asunto de nivel mas bajo...


En línea

El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.
@XSStringManolo
Hacker/Programador
Colaborador
***
Desconectado Desconectado

Mensajes: 2.397


Turn off the red ligth


Ver Perfil WWW
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #2 en: 2 Octubre 2019, 17:05 pm »

Yo por lo que ten entendido en C++ operadores unarios en preincremento la ejecución es más rápida. No sé nada más.
En línea

Mi perfil de patrocinadores de GitHub está activo! Puedes patrocinarme para apoyar mi trabajo de código abierto 💖

engel lex
Moderador Global
***
Desconectado Desconectado

Mensajes: 15.514



Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #3 en: 2 Octubre 2019, 17:10 pm »

Yo por lo que ten entendido en C++ operadores unarios en preincremento la ejecución es más rápida. No sé nada más.

XD claramente entonces lo que sabes es mito XD

no es mas rápida en ningún sentido
En línea

El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.
@XSStringManolo
Hacker/Programador
Colaborador
***
Desconectado Desconectado

Mensajes: 2.397


Turn off the red ligth


Ver Perfil WWW
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #4 en: 2 Octubre 2019, 18:23 pm »

XD claramente entonces lo que sabes es mito XD

no es mas rápida en ningún sentido
https://stackoverflow.com/questions/24901/is-there-a-performance-difference-between-i-and-i-in-c
En línea

Mi perfil de patrocinadores de GitHub está activo! Puedes patrocinarme para apoyar mi trabajo de código abierto 💖

MAFUS


Desconectado Desconectado

Mensajes: 1.603



Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #5 en: 2 Octubre 2019, 19:04 pm »

Sí, parece que el postincremento genera más instrucciones.

Según https://godbolt.org que trabaja con gcc, si no se dan optimizaciones tenemos que el siguiente código
Código
  1. int main() {
  2.    int a = 0;
  3.    int b = 0;
  4.    int m = ++a;
  5.    int n = b++;
  6. }

se traduce a
Código
  1. main:
  2.        ; prepara el stack para la función
  3.        push    rbp
  4.        mov     rbp, rsp
  5.        ; int a = 0;
  6.        mov     DWORD PTR [rbp-4], 0
  7.        ; int b = 0;
  8.        mov     DWORD PTR [rbp-8], 0
  9.        ; int m = ++a;
  10.        add     DWORD PTR [rbp-4], 1
  11.        mov     eax, DWORD PTR [rbp-4]
  12.        mov     DWORD PTR [rbp-12], eax
  13.        ; int n = ++b;
  14.        mov     eax, DWORD PTR [rbp-8]
  15.        lea     edx, [rax+1]
  16.        mov     DWORD PTR [rbp-8], edx
  17.        mov     DWORD PTR [rbp-16], eax
  18.        ; código para regresar de la función
  19.        mov     eax, 0
  20.        pop     rbp
  21.        ret

Cómo se puede ver con el postincremento hay una instrucción de más.
En línea

engel lex
Moderador Global
***
Desconectado Desconectado

Mensajes: 15.514



Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #6 en: 2 Octubre 2019, 19:20 pm »

cuidado con el vicio de la falsa optimizacion... el orden de los factores afecta, se pueden hacer operaciones extras...

https://godbolt.org

Código
  1. int main() {
  2.    int a = 0;
  3.    int b = 0;
  4.    int n = b++;
  5.    int m = ++a;
  6. }

Código
  1.  
  2.        push    rbp
  3.        mov     rbp, rsp
  4.        ; int a = 0;
  5.        mov     DWORD PTR [rbp-4], 0
  6.        ; int b = 0;
  7.        mov     DWORD PTR [rbp-8], 0
  8.        ; int n = ++b;
  9.        mov     eax, DWORD PTR [rbp-8]
  10.        lea     edx, [rax+1]
  11.        mov     DWORD PTR [rbp-8], edx
  12.        mov     DWORD PTR [rbp-12], eax
  13.        ; int m = ++a;
  14.        add     DWORD PTR [rbp-4], 1
  15.        mov     eax, DWORD PTR [rbp-4]
  16.        mov     DWORD PTR [rbp-16], eax
  17.        mov     eax, 0
  18.        ; código para regresar de la función      
  19.        pop     rbp
  20.        ret
  21.  
  22.  

En línea

El problema con la sociedad actualmente radica en que todos creen que tienen el derecho de tener una opinión, y que esa opinión sea validada por todos, cuando lo correcto es que todos tengan derecho a una opinión, siempre y cuando esa opinión pueda ser ignorada, cuestionada, e incluso ser sujeta a burla, particularmente cuando no tiene sentido alguno.
RayR

Desconectado Desconectado

Mensajes: 239


Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #7 en: 2 Octubre 2019, 19:33 pm »

Como ya habrás visto, el problema se debe a que C++ no especifica(ba) un orden de evaluación. Que esa línea funcione o no, dependería del compilador e incluso en un mismo compilador, el nivel de optimización seleccionado o incluso el código concreto en el que se use, podría alterar el resultado, precisamente porque, al no estar definido el orden de evaluación, los compiladores pueden hacerlo como quieran. Sin embargo esto ya se subsanó en C++17, por lo que si compilas para este estándar, aquí:

Código
  1.            numbers[previousIndex+1] = numbers[previousIndex--];
tienes garantizado que primero se evaluará lo de la derecha (previousIndex--) y obtendrás resultados consistentes. Aún así, yo recomendaría que hagas el decremento en su propia línea, tanto porque así tu programa funcionará en cualquier versión de C++, como porque es menos ambiguo para el programador; no todos están familiarizados con C++17, y aunque lo estemos, se nos pueden olvidar las reglas de evaluación.

Lo del incremento/decremento quizás amerita algo más de detalle. Primero, en C y C++, los operadores de preincremento no son más eficientes que los post. La confusión viene debido a que, en el caso de los objetos (a diferencia de los tipos básicos como int, long, etc.), al implementar la sobrecarga de dichos operadores, en el postincremento/decremento se debe crear una copia del objeto, lo que es menos eficiente. Pero recordar que con los objetos no estamos invocando a los operadores de incremento/decremento, sino a funciones que sobrecargan esos operadores, que no es lo mismo. Sin embargo, si no nos interesan los detalles internos, basta decir que el preincremento es más eficiente únicamente en el caso de objetos. Para variables de tipos básicos, no hay diferencia.

En realidad, como curiosidad, y contrario a la engañosa creencia antes mencionada, en algunos casos el postincremento/postdecremento (con tipos básicos) en teoría podría ser más eficiente que el preincremento, ya que el preincremento genera una dependencia de datos que no existe con el post, por lo que en este último caso podría haber un mejo aprovechamiento de los pipelines del procesador. Eso sí, en la práctica yo nunca lo he visto, pero he leído de dos programadores que lo afirman.

Edito: como dice engel lex, cuidado con las falsas optimizaciones. No podemos simplemente contar las instrucciones generadas y sacar conclusiones. Hay muchos factores en juego y para poder evaluar correctamente el rendimiento hay que conocer con cierta profundidad la arquitectura para la que se está programando, así como los patrones de uso concretos del programa a evaluar. Suponer que menos instrucciones == mayor velocidad no tiene sentido. De hecho, hace unos días aquí en otro tema del foro hablé de una función en la cual modifiqué el ensamblador generado por el compilador. El resultado fue una función con 4 o 5 instrucciones adicionales, pero que es un 20% más rápida que la original. Y esto no es ningún caso aislado, de hecho es muy común.
« Última modificación: 2 Octubre 2019, 19:42 pm por RayR » En línea

K-YreX
Moderador
***
Desconectado Desconectado

Mensajes: 1.008



Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #8 en: 3 Octubre 2019, 21:37 pm »

aqui hay un tema que se explicó con un poco de detalle el asunto relacionado a los incrementos/decrementos post/pre, esto es un problema comun a descubrir cuando se está aprendiendo

https://foro.elhacker.net/buscador-t483138.0.html

no es un asunto de java vs c++ es un asunto de nivel mas bajo...
Gracias por el enlace. No sabía nada de los sequence points. Lo de Java vs C++ lo decía porque aun suponiendo que cada lenguaje funciona con sus normas, pensaba que seguirían un mismo estándar de orden de procesamiento. Pero como explico más adelante parece que cada uno usa un orden diferente.

tienes garantizado que primero se evaluará lo de la derecha (previousIndex--) y obtendrás resultados consistentes. Aún así, yo recomendaría que hagas el decremento en su propia línea, tanto porque así tu programa funcionará en cualquier versión de C++, como porque es menos ambiguo para el programador; no todos están familiarizados con C++17, y aunque lo estemos, se nos pueden olvidar las reglas de evaluación.
Ya he visto que es mejor separar los incrementos/decrementos en líneas diferentes pero quiero terminar de entenderlos. He realizado otra prueba con:
Código
  1. int numeros[5] = {1,2,3,4,5};
  2. int j = 2;
  3. numeros[j+1] = -numeros[j--];
Y el resultado es: numeros = {1,2,-3, 4,5}. Por lo que saco que primero se obtiene el valor almacenado en <numeros[j]>, luego se decrementa <j> y después se calcula la posición de memoria del valor <numeros[j+1]>. Como el índice <j> se decrementa antes de calcular la posición de memoria pues la posición vuelve a ser la misma al sumarle 1 al índice. (Todo esto compilado con -std=c++17)

Sin embargo, en Java si probamos a hacer lo mismo:
Código
  1. int numeros[] = {1,2,3,4,5};
  2. int j = 2;
  3. numeros[j+1] = -numeros[j--];
El resultado es: numeros = {1,2,3,-3,5}. Por lo que aquí parece que antes de realizar el decremento ya tiene la posición de memoria donde lo va a guardar calculada y por eso el decremento no se junta con el +1 llegando de nuevo a la misma posición como ocurre en C++.

Estoy estudiando la precedencia de los operadores y por eso me ha sorprendido que el resultado fuera diferente teniendo en cuenta que los incrementos/decrementos tienen más preferencia que las asignaciones.
También he visto en algunos sitios que los operadores en postfijo tienen más preferencia que en prefijo... pero en otros sitios aparece como que tienen la misma prioridad. No sé si esto depende también del estándar.
Y la última pregunta es: en una sentencia con postincremento, el incremento se produce cuando ya se ha terminado de ejecutar la sentencia (línea completa) o justo después de usar el valor sin incrementar?
En línea

Código
  1. cout << "Todos tenemos un defecto, un error en nuestro código" << endl;
RayR

Desconectado Desconectado

Mensajes: 239


Ver Perfil
Re: Operadores incremento/decremento en prefijo y postfijo.
« Respuesta #9 en: 4 Octubre 2019, 00:29 am »

El operador de postincremento/decremento sí tiene mayor prioridad que la asignación, pero ése no es el problema. Lo que aquí entra en juego es lo que pasa antes de la asignación. Si tenemos algo así (un ejemplo más sencillo, para simplificar):

Código
  1. numeros[n] = n++;

obviamente, antes de la asignación, se debe determinar qué es lo que vamos a modificar (operando izquierdo),y qué valor le vamos a dar (derecho), es decir, se deben evaluar ambos lados de la operación. La cuestión es ¿cuál se evalúa primero? Antes estaba indefinido, pero C++17 dice que el de la derecha. Ése es precisamente el cambio relevante aquí.

Y el resultado es: numeros = {1,2,-3, 4,5}. Por lo que saco que primero se obtiene el valor almacenado en <numeros[j]>, luego se decrementa <j> y después se calcula la posición de memoria del valor <numeros[j+1]>. Como el índice <j> se decrementa antes de calcular la posición de memoria pues la posición vuelve a ser la misma al sumarle 1 al índice. (Todo esto compilado con -std=c++17)

Correcto. Podemos distinguir dos partes en la evaluación de n++ (como la de cualquier expresión): el cálculo del valor de retorno de la expresión, y sus efectos colaterales. En el caso de n++, lo que retorna dicha expresión es el valor de que tiene n al momento de evaluarse, y el efecto es la modificación de la propia variable n. Las dos cosas deben ocurrir en ese orden. Por eso, cuando se evalúa la parte derecha de tu asignación, como j vale 2, queda -numeros[2]. Cuando se termina de evaluar la expresión completa de la derecha (incrementar j, tomar el valor del elemento 2 de numeros, y negarlo), se procede a evaluar la parte izquierda, y como j ahora vale 1, queda numeros[1+1].

Los operadores en posfijo tienen mayor prioridad.

Cita de: YreX-DwX
Y la última pregunta es: en una sentencia con postincremento, el incremento se produce cuando ya se ha terminado de ejecutar la sentencia (línea completa) o justo después de usar el valor sin incrementar?

Bueno, en realidad eso no se define por sentencias sino por lo que se conocía como sequence points, pero resulta que incluso ese término es algo impreciso en C++ y ha quedado en desuso desde hace tiempo. Hasta donde sé, lo que preguntas sigue estando sin especificar, pues el estándar dice que, a menos que se indique lo contrario, el orden de los efectos colaterales de las expresiones y subexpresiones se considera "no especificado", y que yo sepa, sólo señala lo que sucede en casos concretos (como cuando expresiones tipo n++ se usan como argumentos a funciones, o con el operador ternario) pero no de forma general. Por eso, incluso en C++17, esto da un resultado indefinido (reiterar que el cambio en C++17 referido en este post habla sólo del orden de evaluación de los dos "lados" de una asignación):

Código
  1. n = n++ + n;

De cualquier forma, si nos limitamos a usos correctos, es bastante irrelevante el orden. Lo mejor es, ante la duda, hacer el incremento en su propia línea; el compilador no va a generar código menos (o más) eficiente por ello.

Se me olvidaba, en Java obtienes otros resultados simplemente porque sus reglas son diferentes a las de C++. En Java sí se especifica lo que debe suceder, y ahí el orden es de izquierda a derecha y, si no recuerdo mal, los efectos colaterales suceden de forma inmediata. No hay realmente nada de especial en como lo hace C++. Es simplemente que este lenguaje (igual que C) siempre ha sido demasiado permisivo, y deja muchas cosas indefinidas o no especificadas, muchas veces en aras de la portabilidad o para permitir que cada implementación pueda ser lo más eficiente posible.
« Última modificación: 5 Octubre 2019, 05:46 am por RayR » En línea

Páginas: [1] 2 Ir Arriba Respuesta Imprimir 

Ir a:  

Mensajes similares
Asunto Iniciado por Respuestas Vistas Último mensaje
Prefijo – Postfijo – Infijo (C#)
.NET (C#, VB.NET, ASP)
ahome31 0 6,626 Último mensaje 2 Noviembre 2009, 06:37 am
por ahome31
Pregunta tonta sobre incremento, decremento y operaciones
Java
monsefoster 3 3,821 Último mensaje 1 Diciembre 2009, 23:17 pm
por Caballero Maldito
ayuda!! infijo a postfijo con 1 o mas digitos.
Java
isaaclm 1 3,856 Último mensaje 1 Junio 2017, 16:39 pm
por Serapis
Operadores de Incremento y Decremento en Python
Programación General
Ali Baba 3 14,905 Último mensaje 13 Septiembre 2017, 17:32 pm
por engel lex
Incremento y decremento de tamaño de figura
Java
Leo066 1 3,010 Último mensaje 27 Octubre 2021, 16:33 pm
por sapito169
WAP2 - Aviso Legal - Powered by SMF 1.1.21 | SMF © 2006-2008, Simple Machines