[ random @ 10.10.2002. 17:00 ] @
Code:
random@galeb:~/code/test$ gcc -g -o prazna prazna.c

random@galeb:~/code/test$ gdb prazna

GNU gdb 5.0

...
(gdb) list main

1    int main() {

2        return 0;

3    }

(gdb) disass main

Dump of assembler code for function main:

0x804839c <main>:    push   %ebp

0x804839d <main+1>:    mov    %esp,%ebp

0x804839f <main+3>:    xor    %eax,%eax

[b]0x80483a1 <main+5>:    jmp    0x80483a4 <main+8>

0x80483a3 <main+7>:    nop [/b]

0x80483a4 <main+8>:    leave  

0x80483a5 <main+9>:    ret    

End of assembler dump.

(gdb)


Zašto kompajler, kada se ne navode swithcevi za optimizaciju, generiše beskoristan markiran kod?

Podpitanje: zašto se prazni EAX registar pre izlaska iz funkcije?

Testirano na Linuxu na dual P2 mašini i na FreeBSD-u na P1 i P2 mašinama.
[ Ivan Dimkovic @ 10.10.2002. 17:19 ] @
A propo nopova, generalno ne mora da bude sporiji, nop-ovi se ubacuju u slucaju da se tako postize bolji flow kroz pipeline i paralelizaciju instrukcija. Mada u ovom konkretnom slucaju to zanemari, jer nema logike da se tako nesto stavlja, nema ni koda :)

A praznjenje eax-a je valjda zbog "return 0" jer je xor eax, eax isto sto i mov eax, 0 a zahteva jedan ciklus manje :) Zameni "return 0" u "return 1" i videces da ce u tom slucaju biti "mov eax, 1"

Sto se jmp-a tice, ovo mi stvarno nije jasno... jedino sto mi pada na pamet je da je ovo default epilog koji gcc stavlja u svaku funckiju zbog nekih akrobacija ako funkcija ima kondicionalno vracanje vise mogucih varijabli.. pa sam patchuje kod - mada mi sve to glupo zvuci, verovatno nije ni tacno.

Probaj da iskompajliras sa Intelovim ICL kompajlerom sa max. optimizacijama pa uporedi kod.


MSVC generise:

Code:

_main   PROC NEAR                                       ; COMDAT

; 3    :        return 0;

        xor     eax, eax

; 4    : }

        ret 0

[ random @ 10.10.2002. 19:15 ] @
To je i meni palo prvo na pamet, da je razlog izbegavanje grešaka pri paralelnom izvršavanju instrukcija, ali to ne drži vodu, obzirom da moderni procesori imaju a) algoritme za predikciju grananja i ispravljanje grešaka pri paralelnom izvršavanju b) mnogo više od dve jedinice za paralelno izvršavanje instrukcija (npr. Athlon valjda ima 7), pa se efektivno ništa ne dobija.

Inače optimizovan binary (sa -O ili jače) nema dotičnu JMP instrukciju.

Code:
push %ebp
mov %esp,%ebp
xor %eax,%eax
leave
ret


P.S. Da, tačno, u EAX je povratna vrednost programa, nisam razmišljao u trenutku pisanja.
[ Ivan Dimkovic @ 10.10.2002. 22:10 ] @
Nista... onda ostaje jedino objasnjenje za taj jmp/nop

da bi kod bio 31337 - GNU C 0w3 YoU@#^

:) Nista pametnije mi ne pada na pamet...
[ leka @ 11.10.2002. 00:55 ] @
Ja priznajem da pojma nemam o asembleru ali imam nesto malo pojma o ELF-u, tako da pretpostavljam da GCC na taj nacin "priprema" program za ELF format...
Code:

[root@pug /root]# gcc -g -o test test.c    
[root@pug /root]# file test
test: ELF 32-bit LSB executable, Intel 80386, version 1, dynamically linked (uses shared libs), not stripped

Mozda lupam, mozda ne...
[ Ivan Dimkovic @ 11.10.2002. 07:10 ] @
Ne verujem, output executable format je stvar linkera a ne kompajlera, a cak i da postoji neki zahtev za aligment-om to bi uradio linker koji bi uradio zero padding na 1/2/4/8/16K boundary a ne kompajler sa besmislenim kodom :)

Sa stanovista CPU-a je krajnje nebitno koji je exec. format, bitno je da se kod ucita i mapira na neku memorijsku lokaciju. A i obrnuto, executable format ne zanima da li funkcija ima neki logican kod ili ne.

Uostalom, ako stavis max optimizaciju besmisleni kod nestaje a format je i dalje ELF, tako da to sigurno nije.



[ Mikky @ 12.10.2002. 00:39 ] @
Citat:
Ivan Dimkovic:
A propo nopova, generalno ne mora da bude sporiji, nop-ovi se ubacuju u slucaju da se tako postize bolji flow kroz pipeline i paralelizaciju instrukcija. Mada u ovom konkretnom slucaju to zanemari, jer nema logike da se tako nesto stavlja, nema ni koda :)


kako to "nop postize bolji flow kroz pipeline i paralelizaciju instrukcija"?
ma sta to znacilo :)
gde se moze vise saznati o ovim stvarima?
[ Ivan Dimkovic @ 12.10.2002. 08:07 ] @
Pa kao sto random rece - na novim Intel/AMD masinama se to radi manje-vise automatski, ali neke DSP arhitekture i malo stariji x86 procesori imaju dobit. Ovim se izbegava tzv. "pipeline stall" kada procesor paralelizuje izvrsavanje nekih instrukcija i kada dodje do "trke" - tj. kad sledeca instrukcija ceka na rezultat prosle. Moderni CPU-ovi imaju bafere i jos dosta alatki za izbegavanje "kocenja"

http://www.cim.mcgill.ca/~fran...-304-427/messages/node104.html

Takodje, i redosled instrukcija moze igrati vrlo veliku ulogu. Intel kompajleri imaju citav set optimizacionih tehnologija koji otklanjaju "uska grla" u x86 kodu, a postoji i alat VTune koji ce ukazati na svaki potencijalni bottleneck (flow dependency, unaligned access, loop unrolling mogucnost, SSE/MMX mogucnosti, itd..)

No ovo je skroz offtopic - nema veze sa besmislenim kodom koji GCC ubacuje u epilog funkcije..
[ tOwk @ 12.10.2002. 10:04 ] @
Citat:
10:43:52 @ ~/tmp > gcc -c -g -o aproba aproba.c
10:44:05 @ ~/tmp > gdb aproba
...
(gdb) list main
1 int main() {
2 return 0;
3 }
(gdb) disass aproba
No symbol "aproba" in current context.
(gdb) disass main
Dump of assembler code for function main:
0x0 <main>: push %ebp
0x1 <main+1>: mov %esp,%ebp
0x3 <main+3>: xor %eax,%eax
0x5 <main+5>: jmp 0x7 <main+7>
0x7 <main+7>: leave
0x8 <main+8>: ret


NOP-a nema na dvoprocesorskoj PIII mašini (isto je i bez ,,-c'', odnosno kada se i linkuje).

A zašto se ubacuje (odnosno koristi) JMP instrukcija, mislim da objašnjava sledeće:
Citat:

(gdb) list main
1 int main() {
2 if (1==0)
3 return 0;
4 else return 1;
5 }
(gdb) disass main
Dump of assembler code for function main:
0x0 <main>: push %ebp
0x1 <main+1>: mov %esp,%ebp
0x3 <main+3>: jmp 0x10 <main+16>
0x5 <main+5>: xor %eax,%eax
0x7 <main+7>: jmp 0x17 <main+23>
0x9 <main+9>: jmp 0x17 <main+23>
0xb <main+11>: nop
0xc <main+12>: lea 0x0(%esi,1),%esi
0x10 <main+16>: mov $0x1,%eax
0x15 <main+21>: jmp 0x17 <main+23>
0x17 <main+23>: leave
0x18 <main+24>: ret


Znači, JMP se koristi da se obezbedi ,,Jedna Tačka Izlaska'' (znači da nemamo 100 leave/ret-ova po funkciji). To izgleda sasvim logično kada imamo sve više grananja, a ove jednostavne probleme i optimizacija rešava u prvom stupnju (optimizovan, ovaj je iste veličine kao i prethodni, samo vraća 1 umesto 0).

Znači, čini mi se da se radi o najopštijem slučaju (a optimizacija je zadužena za ostalo).

Naravno, ovo je samo moja procena, a šta se zapravo dešava ne znam. Znam da stariji GCC (2.8) očekuje od asemblera da ubaci NOP ,,zbog grananja'' (citat sa http://www.cag.lcs.mit.edu/raw/memo/10/rgcc.html, nije verodostojan).

A osim vaših objašnjenja (o dopunjavanju radi istovremenog izvršavanja većeg broja instrukcija), i ovog gore nepouzdanog citata, nemam druge ideje.

Pozdrav

PS. Upotreba ,,gcc -S'' (asembler izlaz) navodi na misao da se neka poravnjavanja ipak odigravaju (align...). Ko bolje zna od mene, više će mu i kod reći.
[ leka @ 13.10.2002. 18:29 ] @
tOwk, hvala na ovom objasnjenju, malo smo svi naucili neke zanimljive stvari. :) Mada obicnom programeru ove stvari ne trebaju, to je svima jasno.
[ Ivan Dimkovic @ 13.10.2002. 18:46 ] @
Pa dobro, malo znanja o procesoru i kompajleru nikom nije na odmet - cak i da se taj neko ne bavi optimizacijom koda :)