[ sportbili @ 15.09.2002. 15:21 ] @
Sta se ovde desava?
U nekom clanku na internetu sam nasao sledece

Citat:

26. Under my compiler, the code "int i = 7; printf("%d\n", i++ * i++);"
prints 49. Regardless of the order of evaluation, shouldn't it
print 56?

A: The operations implied by the postincrement and postdecrement
operators ++ and -- are performed at some time after the operand's
former values are yielded and before the end of the expression, but
not necessarily immediately after, or before other parts of the
expression are evaluated.



Code:

int main() {

int a=7;
printf("%d\n", a++ * a++);
printf("%d %d %d\n", a++, a++, a++); // <------
}


Postavila su se dva pitanja. Prvo, tvrdnja iz clanka nije tacna jer dobijam bas
56 sto znaci da je a u drugom izrazu (a++) vec dobila drugu vrednost. Pitanje glasi
da li to zavisi od kompajlera ili neceg drugog? Tekst je iz 1991. znaci nakon ANSI C-a

Drugo se tice printf-a. Kada izvrsim program drugi printf izbacuje
11 10 9
znaci obrnuto od onoga sto sam ocekivao (9 10 11). Zasto?

[ Dragi Tata @ 15.09.2002. 23:19 ] @
1. I na mom kompajleru (VC.7.0) izbacuje 49, a to bi i trebalo po standardu. Koji kompajler ti koristiš?

2. Na mom kompajleru, rezultat je 7, 7, 7 a a je 10 posle poziva printf - opet onako kako treba da bude...
[ random @ 16.09.2002. 00:42 ] @
Jako zanimljivo. GCC će ih takođe ispisati obrnutim redom. Evo odgovarajućeg asm koda:

Code:
movl   $0x7,0xfffffffc(%ebp)  ; snimamo vrednost "7" negde u data segment
; sad se spremamo da pozovemo printf, a parametre stavljamo na stek obrnutim redosledom
mov    0xfffffffc(%ebp),%eax ; treći parametar ide na stek (ono treće a++);
push   %eax ; pa odatle "7" ide na stek
incl   0xfffffffc(%ebp) ; post-inkrementira se treći parametar (sad je 8)
mov    0xfffffffc(%ebp),%eax ; e ovde je caka, jer iz tačke gledišta kompajlera, svi parametri su isti, pa se koristi isti podatak (već post-inkrementiran, iznosi 8)
push   %eax ; "8" ide na stek
incl   0xfffffffc(%ebp) ; ponovo se inkrementira isti taj podatak
mov    0xfffffffc(%ebp),%eax ; i još jednom sve isto, za prvi parametar
push   %eax ; "9" ide na stek
incl   0xfffffffc(%ebp)
push   $0x401040
call   0x401100 <printf>


Na kraju kad printf() pokupi parametre, on ih kupi obruntim redosledom od onog kojim su stavljani (LIFO disciplina), znači prvo 9, pa 8, pa 7. Problem leži u tome što korisnik/programer pretpostavlja da se parametri procesiraju sa leva na desno, što nije slučaj.

Pravo rešenje bi bilo da se naprave posebne kopije svakog parametra u memoriji, bez obzira da li su isti ili ne, a ne da se inkrementira jedan te isti podatak dvaput pri evaluaciji izraza. Dakle problem je kod kompajera.

Zanimljivo je da će ih GCC, čak i kad se specificira -ansi flag, ispisati brojeve obrnutim redosledom. Zaista čudan propust, ako standard kaže drugačije?

Što se prvog pitanja tiče, sa GCC-om sam dobio 49. Provera dibagerom je pokazala da je prvo pomnožio vrednost samu sa sobom, a zatim je dvaput inkrementirao.
[ Dragi Tata @ 16.09.2002. 01:29 ] @
Ako ćemo po asembleru, evo šta uradi VC.NET u release izdanju:

Code:

00401000  push        7    
00401002  push        7    
00401004  push        7    
00401006  push        offset string "%d %d %d\n" (4060ECh) 
0040100B  call        printf (401020h) 
00401010  add         esp,10h 


Dakle, kompajler je dovoljno pametan da zaključi da mu vrednost promenljive ne treba posle štampanja, pa je nije ni povećavao.

U debug verziji:

Code:

00411A2E  mov         dword ptr [a],7 
00411A35  mov         eax,dword ptr [a] 
00411A38  mov         dword ptr [ebp-0D0h],eax 
00411A3E  mov         ecx,dword ptr [a] 
00411A41  add         ecx,1 
00411A44  mov         dword ptr [a],ecx 
00411A47  mov         edx,dword ptr [a] 
00411A4A  mov         dword ptr [ebp-0D4h],edx 
00411A50  mov         eax,dword ptr [a] 
00411A53  add         eax,1 
00411A56  mov         dword ptr [a],eax 
00411A59  mov         ecx,dword ptr [a] 
00411A5C  mov         dword ptr [ebp-0D8h],ecx 
00411A62  mov         edx,dword ptr [a] 
00411A65  add         edx,1 
00411A68  mov         dword ptr [a],edx 
00411A6B  mov         eax,dword ptr [ebp-0D0h] 
00411A71  push        eax  
00411A72  mov         ecx,dword ptr [ebp-0D4h] 
00411A78  push        ecx  
00411A79  mov         edx,dword ptr [ebp-0D8h] 
00411A7F  push        edx  
00411A80  push        offset string "%d %d %d\n" (4240B8h) 
00411A85  call        @ILT+1150(_printf) (411483h) 
00411A8A  add         esp,10h 


U debug verziji je ispisao 9,8,7 , hehehe...

Uglavnom, zanimljiva "gimnastika za mozak", ali najtoplije preporučujem da se ovakve konstrukcije izbegavaju kod ozbiljnog rada.
[ Milan Aksic @ 16.09.2002. 02:05 ] @
I Borlandov kompajler ce ispisati vrednosti na isti nacin.
Inace zanimljivon posle drugog poziva printf-a u VC 6 a je 9, 9, 9, i ovde se postfiksni operater primenjuje u drugom pozivu printf-a od prvog do zadnjeg parametra, i u eventualnom sledecem pozivu postavlja tacnu vrednost 12, a ne od operanda do operanda.
[ sportbili @ 16.09.2002. 03:18 ] @
Citat:

1. I na mom kompajleru (VC.7.0) izbacuje 49, a to bi i trebalo po standardu. Koji kompajler ti koristiš?


Borland C/C++ 3.1 Compiler :-)

Matoro ali sluzi. Nego trazio sam po netu tutoriale za asm za pocetnike
pa ako neko zna neki, link bi dobro dosao (ja sam nasao neke ali
mozda imate preporuku?)

Pozdravi
[ sspasic @ 16.09.2002. 18:06 ] @
Tu je ispravan odgovor (po ansi) 49 ili 56, zavisno od implementacije.
C za sve operatore (sem || i &&) ne odredjuje redosled kojim se podizrazi izracunavaju.
Jedino sto garantuje kod i++ je da ce se vrednost uvecati POSLE izracunavanja vrednosti i (ali ne mora odmah posle), a ne posle izracunavanja vrednosti i++ * i++.

Slicno vazi i za redosled izracunavanja parametara u pozivu funkcije.

Pogledajte K&R, poglavlje 2.2
[ Rapaic Rajko @ 16.09.2002. 20:51 ] @
Ja, iskreno, nisam toliko dobar poznavalac standarda, ali po nekoj logici, trebalo bi da ispravan rezultat bude 49. Razmislite, sta je logicno u sledecem slucaju?

int a=7, b=7;
printf("%d\n",a++ * b++);

Ocigledno je (bar meni) jedini ispravan rezultat 49. Nazalost, izgleda da moj kompajler (CBuilder) ne misli tako.

Rajko
[ Dragi Tata @ 16.09.2002. 21:02 ] @
Kao što već napomenuh, pitanje je od čisto akademskog značaja. U praksi, printf("%d\n",a++ * b++); i slične egzibicije se smatraju lošim kodom i treba ih izbegavati po svaku cenu. Nikakve koristi od toga, a kod je nečitljiv, teško ga je debugovati, a da i ne pominjem da razni kompajleri (ili čak isti sa promenjenim opcijama za optimizaciju) različito izvršavaju takve instrukcije, kao što se da videti iz prethodnih postova. Jednostavno, batalite to...