• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Świat inkrementacji - problematyka i++ czy ++i

Object Storage Arubacloud
+5 głosów
770 wizyt
pytanie zadane 10 września 2015 w Offtop przez event15 Szeryf (93,790 p.)

Witam.

Chodzi o sytuację, w czasie której dostałem ochrzan od swojego wykładowcy za zastosowanie takiego zapisu:

for(int i = 1; i < 10; i = i + 1)

Postanowiłem bardziej zbadać zagadnienie i sprawdzić jak sprawa ma się w rzeczywistośœci. 
Czy te operacje różnią się między sobą w kodzie maszynowym, czy też nie?

A co z innymi operatorami: 

  • i++
  • ++i
  • i = i + 1
  • i += 1


W tym wypadku postanowiłem stworzyć kod szablonowy w programie C++Builder XE2 kompilowanym na procesory 80836 wyglądający następująco:

int main()
{
     for(int i = 1; i < 10; i++)
     {
         std::cout << i;
     }
     return 0;
}

Oczywśiœcie instrukcja kroku pętli przy każdym z testów została zmieniana odpowiednio.

I tak. W przypadku zapisu

for(int i = 1; i < 10; i++)


Deasemblowany kod wyglądał następująco
 

push  ebp                 ; int main()
mov   ebp,esp
push  ecx
mov   [ebp-$04],$00000001  ; for(int i = 1; i < 10; i++)
push  dword ptr [ebp-$04] ; std::cout << i;
push  dword ptr [$00409208]
call  std::basic_ostream<char, std::char_traits<char> >::operator <<(int)
add   esp,$08
inc   dword ptr [ebp-$04]  ; for(int i = 1; i < 10; i++)
cmp   dword ptr [ebp-$04],$0a
jl    $00401173

Dla zapisu 

for(int i = 1; i < 10; ++i)


Deasemblowany kod wyglądał następująco

push  ebp                 ; int main()
mov   ebp,esp
push  ecx
mov   [ebp-$04],$00000001  ; for(int i = 1; i < 10; ++i)
push  dword ptr [ebp-$04] ; std::cout << i;
push  dword ptr [$00409208]
call  std::basic_ostream<char, std::char_traits<char> >::operator <<(int) 
add esp,$08 
inc dword ptr [ebp-$04] ; for(int i = 1; i < 10; ++i) 
cmp dword ptr [ebp-$04],$0a 
jl $00401173

Dla zapisu 

for(int i = 1; i < 10; i = i + 1)


Deasemblowany kod wyglšdał następująco

push  ebp                 ; int main()
mov   ebp,esp
push  ecx
mov   [ebp-$04],$00000001   ; for(int i = 1; i < 10; i = i + 1)
push  dword ptr [ebp-$04]  ; std::cout << i;
push  dword ptr [$00409208]
call  std::basic_ostream<char, std::char_traits<char> >::operator <<(int) 
add esp,$08 
inc dword ptr [ebp-$04] ; for(int i = 1; i < 10; i = i + 1) 
cmp dword ptr [ebp-$04],$0a 
jl $00401173

Dla zapisu 

for(int i = 1; i < 10; i+= 1)


Deasemblowany kod wyglądał następująco

push  ebp                 ; int main()
mov   ebp,esp
push  ecx
mov [ebp-$04],$00000001   ; for(int i = 1; i < 10; i+= 1)
push dword ptr [ebp-$04]  ; std::cout << i;
push dword ptr [$00409208]
call std::basic_ostream<char, std::char_traits<char> >::operator <<(int) 
add esp,$08 
inc dword ptr [ebp-$04] ; for(int i = 1; i < 10; i+= 1) 
cmp dword ptr [ebp-$04],$0a 
jl $00401173

Każdy na pierwszy rzut oka dostrzega, że nie ma żadnej różnicy pomiędzy tymi 4 skompilowanymi kodami. 
We wszystkich przypadkach otrzymałem dokładnie ten sam kod po deasemblacji. Wniosek mam z tego taki, że niezależnie jaki zapis wybiorę do dodania wartośœci do zmiennej to i tak mój kompilator uzna go za taki sam.

Zrobiłem również mały zwiad wœśród osób, które znają się wiele lepiej ode mnie na programowaniu i dowiedziałem się, że technicznie najszybszym zapisem dla komputera powinno być ++i (które powinno być najbardziej zoptymalizowane ze wszystkich tych zapisów).

++i ma mieć szybsze, bardziej optymalne działanie z tego powodu, że assembler w przypadku i++ tworzy obiekt tymczasowy, który dostanie wartość zmiennej i, a póŸźniej zwiększy mu wartoœść o 1 i na koniec przypisze otrzymany wynik do zmiennej i.

Jednak najwięcej zależy od samego kompilatora. Doskonałym tego przykładem jest ten post:

http://gynvael.coldwind.pl/?id=369


który obrazuje wynik działania instrukcji:
int a = 5;
a = a++ + ++a;
a = ?

 

Wniosek, jaki się nasuwa po przeprowadzeniu tego testu jest taki, że technicznie wszystkie te zapisy są takie same, mogą one się różnić na różnych kompilatorach, ale bazując na tym jednym mogę stwierdzić, że nie robi mu różnicy jaki zapis zostanie użyty, a stosowanie któregoœ z tych zapisów jest kwestią czysto estetyczną.

Jednak czytając post na blogu Gynvaela można dostrzec pewne wady zapisu i++ i ++i w wyrażeniach.

 

7 odpowiedzi

+2 głosów
odpowiedź 10 września 2015 przez event15 Szeryf (93,790 p.)
edycja 10 września 2015 przez event15

KOD ASSEMBLY

Tutaj chciałem napisać pewną rzecz, która wydała mi się doœść ciekawa.
Kiedy piszemy kod pętli for:

for(int i = 1; i < 10 i++) { // instrukcje }

 

To cytując słowa J. Grębosza z książki pt. Symfonia C++ Standard:


Wyjaœśnijmy, co oznaczają poszczególne człony:
  • for (ang. for - dla) oznacza: dla takich warunków rób...
  • int i = 1 - jest to instrukcja inicjalizująca pracę pętli, wykonywane jednokrotnie, zanim zostanie wykonana właœciwa praca pętli (w naszym przykładzie jest to podstawienie i = 0)
  • i < 10 - jest to warunek, czyli wyrażenie, które obliczane jest przed każdym obiegiem pętli. Jeœli jest prawdziwe, to wykonywane zostają instrukcje będące treœcią pętli. (U nas wyrażeniem warunkowym jest wyrażenie: i < 10. Jeśœli rzeczywiœcie owo i jest mniejsze od 10, wówczas wykonywana zostaje instrukcja będąca treśœcią pętli - u nas komentarz // instrukcje)
  • i++ - to instrukcja kroku pętli, wykonywana na zakończenie każdego obiegu (kroku) pętli. Jest to jakby ostatnia instrukcja, wykonywana bezpośrednio przed obliczeniem wyrażenia warunkowego (i <10) - u nas jest to i++

 

Dla kodu:

int main()
{
     for(int i = 1; i < 10; i++)
     {
         std::cout << i;
     }
     return 0;
}

 

Przyjrzyjmy się jego asemblerowej postaci:

push  ebp                 ; int main()
mov   ebp,esp
push  ecx
; Tutaj zaczyna pracować nasza pętla for
1. mov   [ebp-$04],$00000001  ; for(int i = 1; i < 10; i++) 
2. push  dword ptr [ebp-$04] ; std::cout << i;
3. push  dword ptr [$00409208]
4. call  std::basic_ostream<char, std::char_traits<char> >::operator <<(int) 
5. add esp,$08 
6. inc dword ptr [ebp-$04] ; for(int i = 1; i < 10; i++) 
7. cmp dword ptr [ebp-$04],$0a 
8. jl $00401173

 

 

  • Linia 1: Tutaj zmienną i wypełniamy wartoœcią jedynki ($00000001)
  • Linia 2, 2, 4: Wyœświetlamy wartość zmiennej i na ekranie konsoli
  • Linia 5: komenda add esp, $08 oznacza że do ESP (rejestru przechowującego wskaŸźnik wierzchołka stosu) dodajemy $08
  • Linia 6: oznacza zwiększenie wartoœci i o 1; $04 bierze się stąd, że int ma u mnie w komputerze 4 bajty
  • Linia 7: cmp czyli compare. Porównuje wartoœść zmiennej i z wartoœcią odpowiadającą $0A czyli dziesiętnie 10
  • Linia 8: jl czyli jump if lower. Oznacza to dokładnie tyle, że jeśœli wcześœnie porównana wartośœć jest mniejsza niż 10 ($0a) to jest wykonany skok do adresu instrukcji wpisanego po prawo. W naszym przypadku będzie to skok do lini numer 2.


Pozdrawiam wszystkich.

+1 głos
odpowiedź 10 września 2015 przez event15 Szeryf (93,790 p.)
W razie jakichkolwiek wątpliwości, możecie pisać :)
+1 głos
odpowiedź 6 października 2015 przez Patrycjerz Mędrzec (192,320 p.)
Nie ma to jak odpowiedzieć sobie samemu na własne pytanie...
komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
Musiałem bo posty mogą mieć tutaj max 8000 znaków :P a później przypomniałem sobie że nie spytałem się czy ktoś czegoś nie rozumie :P
+1 głos
odpowiedź 6 października 2015 przez adrian17 Ekspert (345,160 p.)

Wniosek, jaki się nasuwa po przeprowadzeniu tego testu jest taki, że technicznie wszystkie te zapisy są takie same, mogą one się różnić na różnych kompilatorach,

Nie, nie, nie. Nie używaj przykładu zawierającego undefined behavior żeby na jego podstawie wnioskować cokolwiek o generacji normalnego kodu.

Drugą rzeczą jest że ++i oraz i++ dla liczb gdy ich wynik nie jest nigdzie przypisywany mają oczywiste zachowanie i kompilator może je sprowadzić do pojedynczej instrukcji nawet z wyłączonymi optymalizacjami. Różnica ++i/i++ ma większe znaczenie w przypadku iteratorów i to przede wszyskim z myślą o nich istnieje rada preferowania ++i.

Trzecia rzecz, rozumiem to co robisz jeśli interesuje Cię generowany kod, ale jeśli mówisz o "optymalności", patrz tylko i wyłącznie na kod wygenerowany po przejściu optymalizacji. Tutaj na oko optymalizacje nie były włączone.

komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
edycja 6 października 2015 przez event15

Tak o takich rzeczach napisał nawet sam Gynvael Coldwind po przeczytaniu tego artykułu :)

https://github.com/hmlb/phpunit-vw

A tutaj podlinkował właśnie ten artykuł, z tym że stronka kolegi już nie działa, ale art można przeczytać tutaj :)
http://gynvael.coldwind.pl/?id=495

komentarz 6 października 2015 przez adrian17 Ekspert (345,160 p.)
(Um, nie wiem co jednoczesne żarciki z afery z VW i PHP mają do mojego komentarza. Zły link?)
komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
Heh już zmieniam, kontener copypaste nie załapał :P

http://gynvael.coldwind.pl/?id=369
komentarz 7 października 2015 przez criss Mędrzec (172,590 p.)
@adrian - dlaczego przy iteratorach, ta różnica ma wieksze znaczenie?
komentarz 7 października 2015 przez event15 Szeryf (93,790 p.)
@Criss - poczytaj linki w moich odpowiedziach:

http://gynvael.coldwind.pl/?id=495
komentarz 7 października 2015 przez adrian17 Ekspert (345,160 p.)

Główna różnica jest taka, że dla iteratorów-klas operatory ++ wywołują metody które mogą mieć różne implementacje - "it++" zazwyczaj wywołuje konstruktor kopiujący iteratora, a kompilator może nie byc w stanie zoptymalizować tego konstruktora (bo na przykład konstruktor kopiujący ma side effecty, np. wywołuje operator new/delete), nawet jeśli kopia iteratora nie jest używana. Przykład dość praktyczny:

#include <boost/filesystem.hpp>

void func(boost::filesystem::directory_iterator &it)
{
     // it++; (1)
     // ++it; (2)
}

Wygenerowany kod z włączonymi optymalizacjami odpowiednio dla (1) i (2): http://hastebin.com/igaqoqiduq.sm, http://hastebin.com/ijuvihitol.sm

komentarz 7 października 2015 przez criss Mędrzec (172,590 p.)
Dzięki bardzo a wytłumaczenie, event i adrian :)
0 głosów
odpowiedź 6 października 2015 przez Schizohatter Nałogowiec (39,600 p.)
Zapis jest czytelniejszy (dla wprawnego oka) i tyle. Wątpię, że był to poważny "ochrzan".
komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
Ten artykuł napisałem kilka lat temu, jak jeszcze byłem w technikum informatycznym - ochrzan był mocny, ale się wybroniłem z tym, że musiałem udowodnić swoje racje :)
0 głosów
odpowiedź 6 października 2015 przez writen Nałogowiec (29,060 p.)
A ja mam bardzo ważne pytanie. Jak się ta sama sprawa ma do języków, które są interpretowane, np. PHP, JS? Czy tutaj sposób w jaki iterujemy po pętli ma znaczenie?

Ja zazwyczaj używam ++i. Szczerze już nie pamiętam dlaczego konkretnie mi się tak przyjęło.
komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
Możliwe, że kiedyś krążyła opinia o większej szybkości tego.

W końcu nie jest (w teorii) tworzony obiekt pośredni :P
komentarz 6 października 2015 przez writen Nałogowiec (29,060 p.)
Kojarzę, że kiedyś widziałem jak ktoś prezentował testy na przykładzie języka javascript, gdzie wyszło, że zapis ++i wykona się najszybciej. Ale raczej różnice czasu nie są tak duże by miały jakiekolwiek znaczenie. To było dawno i może rzeczywiście wtedy coś w tym było. Ale aktualnie nie sądzę, by trzeba było zawracać sobie tym głowę.

Dobrze rozumiem, że ten temat jest sprzed kilku lat?
komentarz 6 października 2015 przez event15 Szeryf (93,790 p.)
Tak, pisałem to bodaj w 2013 roku albo na którymś przełomie tych lat bo pamiętam śnieg :P
komentarz 7 października 2015 przez event15 Szeryf (93,790 p.)

To jest dla $zmienna++:A to jest dla ++$zmienna:

OPCODE FREE zwalnia pamięć zawsze to 1 linijka więcej :D

komentarz 7 października 2015 przez event15 Szeryf (93,790 p.)
Ale o dziwo w PHP $zmienna++ działa dużo szybciej niż ++$zmienna :)
–1 głos
odpowiedź 7 października 2015 przez gromula Stary wyjadacz (10,070 p.)
Powiem szczerze, niby zwykły post o piredółce. ale przez ten artykuł wyjaśniła się sprawa którą "tylko" używam :)
i++ <- będę używał dalej, poprzez popularność i lepszą estetykę :)

Podobne pytania

0 głosów
3 odpowiedzi 1,126 wizyt
+1 głos
1 odpowiedź 277 wizyt
pytanie zadane 26 sierpnia 2016 w Offtop przez Thonem Obywatel (1,230 p.)
0 głosów
2 odpowiedzi 754 wizyt
pytanie zadane 3 lutego 2016 w Offtop przez jako6 Bywalec (2,550 p.)

92,626 zapytań

141,486 odpowiedzi

319,844 komentarzy

62,009 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...