Fructul interzis GOTO este dulce!

O mică excursie istorică

Pentru cei care chiar și fără mine știu perfect ce este un circuit combinațional, un circuit cu memorie și cum a apărut asamblerul din asta - puteți sări în siguranță mai departe - la concluzie.

Și totul a început cu scheme de combinații

interzis
La început era cuvântul - iar cuvântul era funcție. Nu este atât de important că a fost o funcție booleană a unei variabile logice - apoi pe această bază au reușit să implementeze toată (aproape) matematica, apoi textele, grafica ... Fie oricum, s-a dovedit că odată cu cu ajutorul tehnologiei computerului, este foarte convenabil să faceți aritmetică, apoi trigonometrice și alte operații și să găsiți valorile funcțiilor dintr-o variabilă.

Cu alte cuvinte, trebuia să faci un dispozitiv care, după valoarea variabilei (variabilelor), a găsit valoarea funcției.

Pentru a rezolva această problemă cea mai complexă, a fost construit un algoritm secvenţial pentru a efectua operaţii aritmetice (în cazul unei precizii de calcul date într-un astfel de algoritm, fiecare operaţie aritmetică poate fi efectuată într-un ciclu).

Având un algoritm, este ușor să construiți un circuit combinațional - un circuit care instantaneu (până la funcționarea dispozitivelor logice și timpul de propagare a semnalului) la ieșire a dat un răspuns. Întrebare - sunt necesare tranziții? Nu, pur și simplu nu există. Există o succesiune de acțiuni. Toate aceste acțiuni pot fi implementate la final într-un singur ciclu (nu argumentez, va fi foarte, foarte greoi, dar având în vedere adâncimea de biți a tuturor datelor, orice student va construi un astfel de circuit pentru tine - și cu atât mai mult un sintetizator pentru VHDL sau Verilog).

Dar apoi au intervenit circuitele de memorie

interzis
Și apoi capul inteligent al cuiva a venit cu un circuit de feedback - de exemplu, un flip-flop RS. Și apoi a apărut starea circuitului. Iar statul nu este nici unulcare nu este altceva decât valoarea curentă a tuturor elementelor cu memorie.

Apariția unor astfel de elemente de memorie a făcut posibilă realizarea unui salt revoluționar înainte de la dispozitivele hard-coded la automate microprogramate. Mai simplu spus, automatele microprogramate au memorie de instrucțiuni. Există un dispozitiv separat care implementează firmware-ul curent (adunare, scădere sau altceva). Dar alegerea firmware-ului „actual” este gestionată de un dispozitiv separat - să fie un „dispozitiv de eșantionare”.

Este posibil să te descurci fără ele? În nici un caz! Dacă nu folosim tranziții, atunci vom reveni la circuitul combinațional fără memorie.

Drept urmare, am ajuns la asamblator

Micro-, simple și super-calculatoare au devenit apoteoza unor astfel de dispozitive de calcul. Toate se bazează pe un limbaj de cod care este destul de ușor convertit în Assembler cu un set aproximativ identic de instrucțiuni. Să luăm în considerare un anumit asamblator mediu de microcontrolere (sunt familiarizat cu asamblatorul pentru ATmeg, PIC și AT90). Cum este construită munca tranzițiilor?

Declar cu toată responsabilitatea - este imposibil să faci fără operațiuni de tranziție în assembler! Orice program din assembler este plin de ele! Totuși, aici nimeni nu se va certa cu mine, cred că nu o va face.

Ce concluzie se poate trage? La nivelul microprocesorului, operațiunile de salt sunt foarte utilizate. Este aproape imposibil să scrii un program real care să nu le folosească (poate că se poate, dar va fi o super-mega-perversiune și cu siguranță nu un program real!). Nimeni nu se va certa cu asta.

Dar de ce, atunci, în limbile de nivel superior - să ne concentrăm pe C pentru microcontrolere - declarația goto a căzut brusc din favoarea.

Un pic despre algoritmi

interzis
Acum să ne uităm la algoritmul complicat. Habar n-am ce esteprostii – dar trebuie implementat. Vom presupune că acesta este un TK.

Aici A, B, C, D, E sunt nișteoperații, nu un apel de funcție! Este posibil să utilizeze o mulțime de variabile locale. Și este foarte posibil să își schimbe starea. Adică, în acest caz nu vorbim despre funcții de apel - nu vom detalia unele acțiuni.

Iată cum arată cu goto:

Foarte concis și ușor de citit. Dar - nu poți! Să încercăm fără să trecem la:

Ai înțeles ceva din logica celei de-a doua listări. Comparați ambele înregistrări:

  • Am petrecut de 5 ori mai puțin timp la prima listă decât la a doua.
  • Listarea cu goto este de cel puțin 2 ori mai scurtă.
  • O listă cu goto va fi înțeleasă de oricine cu cea mai minimă pregătire în C. Am încercat să o fac pe a doua cât mai accesibilă și cât mai evidentă - și totuși, durează mult timp să mă aprofundez în ea.
  • Cât timp va dura să depanezi prima opțiune și cât de mult să depanezi a doua?
  • Și, în general, dacă luăm în considerare algoritmul desenat ca o declarație a problemei, atunci prima listare este 100% corectă. Despre cel de-al doilea, inca nu sunt foarte sigur... cel putin in ordinea verificarii conditiilor si steagurilor.
  • Comparați codul de asamblare rezultat din prima și a doua listă.
Dar nu există nicio problemă în a doua listă!

De asemenea, mi s-a oferit să implementez acest algoritm aproximativ astfel:

Ei bine, în viață, astfel de algoritmi nu se găsesc aproape niciodată. Să vorbim mai bine despre viață.

goto în programe reale

În cei peste 20 de ani de experiență, am trecut prin mai multe platforme hardware și o duzină de limbaje de programare, am participat la scrierea unui produs software ActiveHDL mare, am realizat o bază de date comercială și multe programe mici pentru depanarea echipamentelor utilizate în Olimpiada.jocuri și, de asemenea, a făcut dispozitive pentru această olimpiadă (deja mai multe Olimpiade, mai exact). Pe scurt, ceva ce scotocesc în programare. A, da, am uitat - am absolvit cu diploma de onoare de la KNURE - adica teoretic am si o flacara.

Prin urmare, reflecțiile și situațiile mele ulterioare... să spunem doar că am un drept moral la ele.

Utilizarea implicită a lui goto

Există multe afirmații în limbajul C care sunt, de fapt, banale goto - condiționate sau necondiționate. Acestea sunt tot felul de bucle for (...), while (...) , do while (...). Aceasta este o analiză a variabilelor numerice comutator (…) . Acestea sunt aceleași declarații break/jump în buclele break și continue. La urma urmei, acestea sunt apeluri funct() și revin de la ele.

Aceste goto sunt considerate „legale” - de ce este goto în sine ilegal?

De ce trebuie acuzat

Este acuzat de faptul că codul devine ilizibil, slab optimizat și pot apărea erori. Este vorba despre dezavantaje practice. Iar cele teoretice sunt doar răi și analfabeți, și atât!

Nu crezi că un cuțit este un lucru foarte periculos? Dar din anumite motive în bucătărie îl folosim. Și 220 de volți este groaznic, cât de periculos! Dar dacă îl folosești cu înțelepciune, poți trăi.

Același lucru este valabil și pentru goto. Trebuie să-l utilizați cu înțelepciune - și apoi codul va funcționa corect.

Și despre argumente teoretice - asta, iartă-mă, este o dispută despre gusturi. Folosești notația maghiară? Nu, nu o suport! Dar nu spun că e rea din cauza asta! Personal, cred că o variabilă ar trebui să aibă o încărcătură semantică - pentru ceea ce a fost creată. Dar nu le voi interzice altor persoane să folosească această metodă de denumire!

Sau există esteți care cred că a scrie a = ++i este analfabet, ar trebui să scrieți i = i + 1; a = i. Și acum ce, să interzic și asta?

Eroare la procesare

Să luăm procesarea pachetelor de intrare de la un dispozitiv extern:

Avem antetul pachetului. Analizat. Da, pachetul „A” înseamnă că trebuie să facem ceva de 10 ori. Nu uităm să controlăm timpul de lucru al acestei secțiuni - ce se întâmplă dacă a doua parte este înghețată? Da, încă s-a blocat - condiția de timeout a funcționat - apoi ieșim afară - de la buclă, de la comutator.

Același lucru se poate face cu ajutorul a tot felul de steaguri - dar va funcționa mai lent, plus că va ocupa o celulă de memorie atât de puțină. Se merită?

Acesta este un caz simplu. Dar receive_byte () poate fi și o funcție macro cu gestionarea timeout-ului. Și vor exista și ieșiri atât de abrupte.

Acesta este exact cazul în care folosesc în mod activ goto. Acest lucru mi-a permis să nu intru în „înghețuri” în cazul unor probleme cu dispozitivele externe, UART, USB etc.

Ieșiți din bucla imbricată în exterior

Uită-te la programul de mai jos:

Ce se întâmplă - este clar? Există o buclă imbricată. Dacă a apărut o condiție, lăsăm toate procesările ulterioare.

Acest cod cu steaguri arată diferit:

Ce sa întâmplat în acest caz? La fiecare iterație, verificăm acum steagul. Nu uitați să verificați. Acestea sunt fleacuri, dacă sunt puține iterații și vorbim de memorie „fără dimensiune” într-un PC. Și când programul este scris pentru microcontroler, totul devine esențial.

Apropo, în legătură cu aceasta, în unele limbi (dacă nu mă înșel, în Java) este posibil să ieși din buclă folosind eticheta break Leave. Apropo, la fel!

Pot da exact același exemplu cu procesarea în switch (...) < caz...>. Mă întâlnesc des atunci când procesez pachetele primite cu structură inegală.

Generarea automată a codului

Ești familiarizat cu automataprogramare? Sau orice altă generare automată de cod? Să zicem, creatorii de manipulatori lexicali (fără a folosi boost-ul greoi::spirit). Toate aceste programe creează cod care poate fi folosit ca o „cutie neagră” - Nu-ți pasă ce este înăuntru; Îți pasă ce face. Și acolo, goto este folosit foarte, foarte des...

Ieși într-un singur loc

În C, uneori trebuie să scrieți ceva de genul:

Acest cod va arăta mult mai ordonat astfel:

Este clară ideea? Uneori trebuie să faci ceva când ieși. Uneori sunt multe de făcut. Și apoi goto ajută foarte mult aici. Am si eu astfel de exemple. Se pare că a enumerat totul, acum puteți rezuma...

Acesta este punctul meu de vedere! Și ea este corectă cu mine. Poate - pentru tine, dar nu te voi forța să-l urmezi!

Deci, este evident pentru mine că goto ajută la rezolvarea unor probleme într-un mod optim și mai bun. Și uneori este invers - goto poate cauza o mulțime de probleme.

Avantajele utilizării goto:

  • cea mai optimă (în ceea ce privește listarea și codul rezultat) iese din mai multe bucle imbricate și comută ... caz
  • C: cea mai economică modalitate (în ceea ce privește listarea și codul rezultat) de a gestiona erorile
  • în cazuri individualecea mai optimă construcție a algoritmului
  • salvează memorie și cicluri cuatențieutilizare, care uneori este de o importanță capitală
Dezavantajele utilizării goto:
  • cod necunoscut
  • încălcarea fluxului de citire a listei de sus în jos și parcurgerea standardizată a blocurilor în cod (în sensul că este posibil să mergeți în centrul blocului, precum și să ieșiți din acesta)
  • complicând compilatorul (și uneori imposibilitatea) procesului de optimizare a codului
  • crescând probabilitatea de a crea erori subtile în cod
Cine altcineva vă va spune argumentele pro/contra? O sa scriu daca sunt justificate.

Încă o dată, vă atrag atenția:Nu vă îndemn să folosiți goto peste tot! DAR, în unele cazuri, vă permite să implementați algoritmul mult mai eficient decât toate celelalte mijloace.