În informatică , un compilator este un program care transformă codul sursă în cod obiect . În general, codul sursă este scris într-un limbaj de programare ( limbajul sursă ), are un nivel ridicat de abstractizare și este ușor de înțeles de către oameni. Codul obiectului este scris în general într-un limbaj de nivel inferior (numit limbă țintă ), de exemplu un limbaj de asamblare sau un limbaj mașină , pentru a crea un program executabil de o mașină.
Un compilator efectuează următoarele operații: analiză lexicală , preprocesare ( preprocesare ), analiză sintactică ( analiză ), analiză semantică și generare de cod optimizată . Compilarea este adesea urmată de un pas de editare a linkurilor , pentru a genera un fișier executabil. Când programul compilat (cod obiect) este executat pe un computer al cărui procesor sau sistem de operare este diferit de cel al compilatorului, acesta se numește încrucișare .
Există două opțiuni de compilare:
Un program sursă în limbaj C.
codul de asamblare corespunzător
programul după compilare - limbajul mașinii afișat în hexazecimal
Primele programe de calculator au fost scrise în limbaj de asamblare . Limbaje de programare cel mai înalt nivel (în straturi de abstractizare ) nu au fost inventate până când beneficiile capacității de a software - ului reutilizarea pe diferite tipuri de procesoare au devenit mai importante decât costul de scris „un compilator. Capacitatea de memorie foarte limitată a computerelor timpurii a pus, de asemenea, mai multe probleme tehnice în dezvoltarea compilatoarelor.
La sfârșitul anilor 1950 , au apărut pentru prima dată limbaje de programare independente de mașini. Ulterior, sunt dezvoltate mai multe compilatoare experimentale. Primul compilator, A-0 System (pentru limba A-0) a fost scris de Grace Hopper în 1952. Se crede că echipa FORTRAN condusă de John Backus de la IBM a dezvoltat primul compilator complet în 1957. COBOL , dezvoltat în 1959 și în mare parte bazat pe ideile lui Grace Hopper, este primul limbaj care a fost compilat pe mai multe arhitecturi.
În mai multe domenii de aplicare , S-a răspândit rapid ideea de a folosi un limbaj cu un nivel mai ridicat de abstractizare. Odată cu creșterea funcționalității acceptate de limbaje de programare mai noi și complexitatea crescândă a arhitecturii computerelor, compilatoarele au devenit din ce în ce mai complexe.
În 1962, primul compilator „ auto-găzduit ” - capabil să compileze în cod obiect, propriul cod sursă exprimat într-un limbaj la nivel înalt - a fost creat pentru Lisp de Tim Hart și Mike Levin la Massachusetts Institute of Technology (MIT). Începând din anii 1970 , a devenit foarte obișnuit să se dezvolte un compilator în limba pe care a fost destinat să o compileze, făcând Pascal și C limbaje de dezvoltare foarte populare.
Putem folosi și un limbaj sau un mediu specializat în dezvoltarea de compilatoare: vorbim în timpul instrumentelor de meta-compilare și folosim de exemplu un compilator de compilare . Această metodă este deosebit de utilă pentru realizarea primului compilator al unui nou limbaj; utilizarea unui limbaj adaptat și riguros facilitează apoi dezvoltarea și evoluția.
Sarcina principală a unui compilator este de a produce codul obiect corect care va rula pe un computer. Majoritatea compilatoarelor permit optimizarea codului, adică vor căuta să îmbunătățească viteza de execuție sau să reducă ocuparea memoriei programului.
În general, limba sursă este „nivel mai înalt” decât limba țintă, adică prezintă un nivel mai ridicat de abstractizare. În plus, codul sursă al programului este de obicei distribuit în mai multe fișiere.
Un compilator funcționează prin analiză-sinteză: în loc să înlocuiască fiecare construcție a limbii sursă cu o serie echivalentă de construcții ale limbii țintă, începe prin analiza textului sursă pentru a construi o reprezentare intermediară pe care, la rândul său, o traduce în limba țintă. .
Compilatorul este separat în cel puțin două părți: o parte frontală (sau frontală), uneori numită „stub”, care citește textul sursă și produce reprezentarea intermediară; și o parte din spate (sau final), care traversează această reprezentare pentru a produce textul țintă. Într-un compilator ideal, partea din față este independentă de limba țintă, în timp ce partea din spate este independentă de limba sursă. Unele compilatoare efectuează o prelucrare substanțială pe partea intermediară, devenind o parte centrală în sine, independentă atât de limba sursă, cât și de mașina țintă. Astfel putem scrie compilatoare pentru o gamă întreagă de limbaje și arhitecturi prin partajarea părții centrale, la care atașăm o parte frontală pentru fiecare limbă și o parte posterioară pentru fiecare arhitectură.
Etapele compilării includ:
Analiza lexicală, sintactică și semantică, trecerea printr-un limbaj intermediar și optimizarea formează partea din față. Generarea și conectarea codului este partea finală.
Aceste etape diferite înseamnă că compilatoarele sunt întotdeauna obiectul cercetării.
De punere în aplicare (realizare concretă) a unui limbaj de programare pot fi interpretate sau compilate. Această realizare este un compilator sau un interpret , iar un limbaj de programare poate avea o implementare compilată și alta interpretată.
Vorbim de compilare dacă traducerea se face înainte de executare (principiul unei bucle este apoi tradus o singură dată) și de interpretare dacă traducerea este terminată pas cu pas, în timpul execuției (elementele unei bucle sunt apoi examinate pentru fiecare utilizare) .
Interpretarea este utilă pentru depanare sau dacă resursele sunt limitate. Compilarea este preferabilă în exploatare.
Primele compilatoare au fost scrise direct în limbaj de asamblare , un limbaj simbolic elementar corespunzător instrucțiunilor procesorului țintă și unor structuri de control puțin mai evoluate. Acest limbaj simbolic trebuie asamblat (nu compilat) și legat pentru a obține o versiune executabilă. Datorită simplității sale, un program simplu este suficient pentru a-l converti în instrucțiuni pentru mașină.
Compilatoarele actuale sunt în general scrise în limba pe care sunt destinate să le compileze; de exemplu un compilator C este scris în C, SmallTalk în SmallTalk, Lisp în Lisp etc. În realizarea unui compilator, se face un pas decisiv atunci când compilatorul pentru limbajul X este suficient de complet pentru a se compila: atunci nu mai depinde de un alt limbaj (chiar și de la asamblare) care urmează să fie produs.
Este dificil să detectezi o eroare a compilatorului. De exemplu, dacă un compilator C are o eroare, programatorii C vor tinde în mod firesc să pună la îndoială propriul cod sursă, nu compilatorul. Mai rău, dacă acest compilator buggy (versiunea V1) compilează un compilator non-buggy (versiunea V2), executabilul compilat (de V1) al compilatorului V2 ar putea fi buged. Cu toate acestea, codul său sursă este bun. Prin urmare, bootstrap-ul necesită programatorilor compilatorilor să ocolească erorile compilatoarelor existente.
Clasificarea compilatoarelor după numărul de treceri se datorează lipsei de resurse hardware a computerelor. Compilarea este un proces costisitor, iar computerele timpurii nu aveau suficientă memorie pentru a deține un program care trebuia să facă această treabă. Compilatoarele au fost astfel împărțite în subprograme pe care fiecare le citește din sursă pentru a finaliza diferitele faze ale analizei lexicale , ale analizei și analizei semantice .
Capacitatea de a combina totul într-o singură trecere a fost văzută ca un avantaj, deoarece simplifică scrierea compilatorului, care, în general, rulează mai rapid decât un compilator cu mai multe treceri. Astfel, datorită resurselor limitate ale sistemelor timpurii, multe limbaje au fost proiectate special astfel încât să poată fi compilate într-o singură trecere (de exemplu, limba Pascal ).
Structura neliniară a programuluiÎn unele cazuri, o anumită caracteristică a limbajului necesită compilatorului său să efectueze mai multe pase. De exemplu, luați în considerare o afirmație din linia 20 a sursei care afectează traducerea unei afirmații din linia 10 . În acest caz, prima trecere ar trebui să colecteze informații despre declarații, în timp ce traducerea efectivă are loc numai în timpul unei treceri ulterioare.
OptimizăriÎmpărțirea unui compilator în programe mici este o tehnică utilizată de cercetătorii interesați să producă compilatoare eficiente. Acest lucru se datorează faptului că dezavantajul compilației cu o singură trecere este că nu permite efectuarea majorității optimizărilor sofisticate necesare pentru a genera cod de înaltă calitate. Apoi devine dificil să se numere exact numărul de treceri pe care le realizează un compilator de optimizare.
Împărțirea demonstrației de corecțieDemonstrarea corectitudinii unei serii de programe mici necesită adesea mai puțin efort decât demonstrarea corectitudinii unui program echivalent mai mare.
Un compilator de compilare este un program care poate genera oricare sau toate părțile unui compilator. De exemplu, puteți compila elementele de bază ale unei limbi, apoi puteți utiliza elementele de bază ale limbajului pentru a compila restul.
În funcție de utilizare și de mașina care va executa un program, poate doriți să optimizați viteza de execuție, ocuparea memoriei, consumul de energie, portabilitatea către alte arhitecturi sau timpul de compilare.
Compilarea încrucișată se referă la lanțuri de compilare capabile să traducă codul sursă în cod obiect a cărui arhitectură de procesor diferă de cea în care se efectuează compilarea. Aceste lanțuri sunt utilizate în principal în IT industrial și în sistemele încorporate .
Unii compilatori traduc un limbaj sursă în limbajul mașinii virtuale (numit limbaj intermediar), adică într-un cod (în general binar) executat de o mașină virtuală : un program care emulează funcționalitățile principale ale unui computer. Se spune că astfel de limbi sunt semi-compilate. Portarea unui program necesită doar portarea mașinii virtuale, care va fi de fapt fie un interpret, fie un al doilea traducător (pentru compilatoarele multi-țintă). Astfel, compilatoarele traduc Pascal în P-Code, Modula 2 în M-Code, Simula în S-code, sau mai recent cod Java în Java bytecode (cod obiect).
Un program scurt în Scala.
Codul de octet Java rezultat, executabil pe mașina virtuală.
Când compilația se bazează pe un cod de octeți, vorbim despre compilare din mers . Apoi folosim mașini virtuale, cum ar fi mașina virtuală Java, cu care putem compila în mod special Scala . Este posibil în unele limbi să utilizați o bibliotecă care să permită compilarea din timp a codului introdus de utilizator, de exemplu în C cu libtcc.
Alți compilatori traduc codul dintr-un limbaj de programare în altul. Ele sunt numite transcompilatoare , sau chiar de anglicism, transpilatoare sau transpilatoare. De exemplu, software-ul LaTeX permite, dintr-un cod sursă din LaTeX, să obțină un fișier în format PDF (cu de exemplu comanda pdflatex sub Ubuntu ) sau HTML . Un alt exemplu, LLVM este o bibliotecă care ajută la construirea de compilatoare, folosită și de AMD pentru a dezvolta „HIP”, un transcompilator de cod CUDA (limbaj specific NVIDIA și utilizat pe scară largă) pentru a-l rula pe procesoare grafice AMD.
Codul sursă.
Codul obținut după compilare.
Previzualizarea documentului pdf.
Unele compilatoare traduc, incremental sau interactiv, programul sursă (introdus de utilizator) în codul mașinii. Putem cita ca exemplu câteva implementări ale Common Lisp (cum ar fi SBCL (en) ).