Cum funcționează mai exact instrucțiunea LOOP din x86? (Programare, Bucle, Asamblare, X86)

Hannah Duncan a intrebat.
            mov    ecx, 16
looptop:    .
            .
            .
            loop looptop

De câte ori se va executa această buclă?

Ce se întâmplă dacă ecx = 0 pentru a începe? Are loop sare sau se încadrează în acest caz?

Comentarii

  • Ce registru este folosit ca numărător al buclei? (indiciu: pare să dețină valoarea imediată 16) –  > Por David C. Rankin.
  • Acestea sunt toate informațiile pe care le primesc pentru această întrebare. –  > Por Hannah Duncan.
  • Al doilea indiciu, CX este cunoscut sub numele de registrul de numărare. (ecx este doar versiunea pe 32 de biți – este utilizat pentru a stoca numărul buclelor în operațiile iterative și este decrementat de către 1 fiecare iterație) – –  > Por David C. Rankin.
  • de ce ar trebui ca 0 să facă o buclă de zero ori? Nu se poate face o buclă de zero ori, CPU nu prevede loop instrucțiunea și sare cumva peste corpul buclei (CPU-ului îi pasă doar de instrucțiunea curentă și de starea sa curentă, nimic altceva), deoarece aceasta este do { ... } while() tip de buclă, se va executa cel puțin o dată. Pentru ecx=1 se va executa exact o dată. Pentru ecx=0 următoarea valoare este, bineînțeles 4294967295, dacă înțelegeți cum funcționează matematica binară și de ce 0-1 va seta toți cei 32 de biți la unu (de ex. 0xFFFFFFFF == 4294967295). Nu folosiți logica fuzzy umană, faceți calcule cu precizie de mașină. Este un calculator, nimic mai mult. –  > Por Ped7g.
  • Vă mulțumesc foarte mult! Am folosit un depanator pentru a încerca să îmi dau seama, dar pur și simplu nu am trecut prin suficient timp pentru a vedea că bucla a rulat de un număr finit de ori. Acum are mult mai mult sens! –  > Por Hannah Duncan.
1 răspunsuri
Peter Cordes

loop este exact ca dec ecx / jnz, cu excepția faptului că nu setează steaguri.

Este ca și cum ar fi partea de jos a unui do{} while(--ecx != 0); în C. Dacă execuția intră în buclă cu ecx = 0, wrap-around înseamnă că bucla va fi executată de 2^32 ori. (Sau de 2^64 ori în modul pe 64 de biți, deoarece utilizează RCX).

Spre deosebire de rep movsb/stosb/etc., nu se verifică dacă ECX=0 înainte de descreștere, ci doar după.

Dimensiunea adresei determină dacă folosește CX, ECX sau RCX. Așadar, în codul pe 64 de biți, addr32 loop este ca dec ecx / jnz, în timp ce un cod obișnuit loop este ca dec rcx / jnz. Sau în codul pe 16 biți, în mod normal se folosește CX, dar un prefix de dimensiune a adresei (0x67) îl va face să folosească ecx. După cum se spune în manualul Intel, nu ține cont de REX.W, deoarece acesta stabilește dimensiunea operand-size, nu dimensiunea adresei.

În legătură cu aceasta: De ce buclele sunt întotdeauna compilate în stilul „do…while” (tail jump)? pentru mai multe informații despre structura buclelor în asm, while(){} vs. do{}while() și cum să le aranjați.


Sfaturi suplimentare pentru depanare

Dacă doriți vreodată să cunoașteți detaliile unei instrucțiuni, consultați manualul: fie manualul oficial de referință al setului de instrucțiuni vol.2 PDF al Intel, fie un extras html cu fiecare intrare pe o pagină diferită (http://felixcloutier.com/x86/). Dar rețineți că HTML-ul omite introducerea și anexele care conțin detalii despre cum se interpretează lucrurile, cum ar fi atunci când se spune că „stegulețele sunt setate în funcție de rezultat” pentru instrucțiuni precum add.

Și puteți (și ar trebui), de asemenea, să încercați pur și simplu chestii într-un depanator: un singur pas și urmăriți cum se schimbă registrele. Utilizați o valoare de pornire mai mică pentru ecx astfel încât să ajungeți la partea interesantă ecx=1 partea interesantă mai repede. Vedeți, de asemenea, wiki-ul x86 tag pentru link-uri către manuale, ghiduri și sfaturi de depanare asm în partea de jos.


Și BTW, dacă instrucțiunile din interiorul buclei care nu sunt afișate modifică ecx, aceasta ar putea să se bucleze de orice număr de ori. Pentru ca întrebarea să aibă un răspuns simplu și unic, aveți nevoie de o garanție că instrucțiunile dintre etichetă și loop instrucțiunea nu se modifică ecx. (Ar putea să o salveze/restaureze, dar dacă aveți de gând să faceți asta, de obicei este mai bine să folosiți un alt registru ca numărător al buclei. push/pop în interiorul unei bucle face ca codul dumneavoastră să fie greu de citit).


Rant despre utilizarea excesivă a LOOP chiar și atunci când trebuie deja să incrementezi altceva în buclă. LOOP nu este singurul mod de a face bucle și, de obicei, este cel mai rău.

În mod normal, nu ar trebui să folosiți niciodată instrucțiunea de buclă, cu excepția cazului în care optimizați pentru dimensiunea codului în detrimentul vitezei, deoarece este lentă. Compilatoarele nu o folosesc. (Așa că furnizorii de procesoare nu se obosesc să o facă rapidă; catch 22.) Utilizați dec / jnz, sau o condiție de buclă complet diferită. (A se vedea și http://agner.org/optimize/ pentru a afla mai multe despre ce este eficient).

Buclele nici măcar nu trebuie să folosească un contor; de multe ori este la fel de bine, dacă nu chiar mai bine, să comparați un pointer cu o adresă finală sau să verificați o altă condiție. (Utilizarea inutilă a loop este una dintre problemele mele, mai ales atunci când aveți deja ceva într-un alt registru care ar funcționa ca un contor de buclă). Utilizați cx ca un contor de buclă de multe ori nu face decât să blocheze unul dintre puținele și prețioasele tale registre, când ai fi putut folosi cmp/jcc pe un alt registru pe care oricum îl incrementați.

IMO, loop ar trebui să fie considerată una dintre acele instrucțiuni x86 obscure cu care începătorii nu ar trebui să fie distrași. Ca și stosd (fără un rep prefix), aam sau xlatb. Cu toate acestea, are utilizări reale atunci când se optimizează dimensiunea codului. (Acest lucru este uneori util în viața reală pentru codul mașinii (cum ar fi pentru sectoarele de boot), nu doar pentru chestii cum ar fi codul golf).

IMO, doar învață / învață cum funcționează ramurile condiționate și cum să faci bucle din ele. Atunci nu veți fi blocați în a crede că există ceva special în legătură cu o buclă care utilizează loop. Am văzut o întrebare sau un comentariu SO care spunea ceva de genul „Credeam că trebuie să declari bucle”, și nu mi-am dat seama că loop era doar o instrucțiune.

</rant>. Așa cum am spus, loop este unul dintre motivele mele de supărare. Este o instrucțiune obscură de golf de cod, cu excepția cazului în care optimizați pentru un 8086 real.

Comentarii

  • Am decis să postez acest lucru doar pentru a avea un răspuns canonic la orice întrebare viitoare „cum se face loop funcționează”. –  > Por Peter Cordes.
  • Acest răspuns are prea multe detalii –  > Por ineedahero.
  • @ineedahero: nu ezitați să vă opriți din citit după prima propoziție sau paragraf, atunci. Asta descrie exact funcționarea sa normală. –  > Por Peter Cordes.
  • Ha! Dacă „prea multe detalii” ar fi o problemă comună. Mulțumesc @PeterCordes, am găsit acest răspuns foarte util și educativ. Aș vrea să remarc totuși că am ajuns aici după ce am găsit loope în instrucțiunile emise de runtime-ul .NET, așa că nu cred că este cazul ca compilatoarele să nu-l folosească. De asemenea, dacă este emisă de runtime-ul .NET, trebuie să fie rezonabil de rapidă, având în vedere cât timp petrece acel grup de profilare și optimizare. –  > Por N8allan.
  • @N8allan: A fost .NET tuning pentru un procesor AMD în acel caz? Sunt curios în ce context, pentru că loop și loope sunt încă lente pe procesoarele Intel. (Vedeți De ce este lentă instrucțiunea loop? Nu ar fi putut Intel să o implementeze eficient?). Este posibil să fi început dezasamblarea de la ceva care nu trebuia să fie începutul unei instrucțiuni, astfel încât dezasamblarea ta a fost nesincronizată pentru o vreme? Sau ești sigur că era de fapt în execuție. –  > Por Peter Cordes.