Preprocesor C/C++ ( eng. preprocesor , preprocessor) - un program care pregătește codul programului în C / C++ pentru compilare .
Preprocesorul face următoarele:
Compilarea condiționată vă permite să alegeți ce cod să compilați pe baza:
Etapele preprocesorului:
Limbajul preprocesorului C/C++ nu este Turing complet, fie doar pentru că este imposibil să blochezi preprocesorul folosind directive. Vezi funcția recursivă (teoria calculabilității) .
O directivă de preprocesor (linie de comandă) este o linie din codul sursă care are următorul format #ключевое_слово параметры:
Lista de cuvinte cheie:
Când directivele #include "..."și sunt găsite #include <...>, unde „...” este un nume de fișier, preprocesorul citește conținutul fișierului specificat, execută directive și substituții (substituții), înlocuiește directiva #includecu o directivă #lineși conținutul fișierului procesat.
Pentru a #include "..."căuta un fișier, se efectuează în folderul curent și folderele specificate pe linia de comandă a compilatorului. Pentru a #include <...>căuta un fișier, se efectuează în foldere care conțin fișiere de bibliotecă standard (căile către aceste foldere depind de implementarea compilatorului).
Dacă se găsește o directivă care #include последовательность-лексем nu se potrivește cu niciuna dintre formele anterioare, aceasta consideră secvența de jetoane drept text, care, ca rezultat al tuturor substituțiilor macro, ar trebui să dea #include <...>sau #include "...". Directiva generată în acest fel va fi interpretată în continuare în conformitate cu formularul primit.
Fișierele incluse conțin de obicei:
Directiva #includeeste de obicei specificată la începutul fișierului (în antet), așa că fișierele incluse sunt numite fișiere antet .
Un exemplu de includere a fișierelor din biblioteca standard C.
#include <math.h> // include declarații de funcție matematică #include <stdio.h> // include declarațiile funcției I/OUtilizarea unui preprocesor este considerată ineficientă din următoarele motive:
Începând cu anii 1970, au început să apară metode care au înlocuit includerea fișierelor. Limbile Java și Common Lisp folosesc pachete (cuvânt cheie package) (vezi pachetul în Java ), Pascal folosește limba engleză. unități (cuvinte cheie unitși uses), în module Modula , OCaml , Haskell și Python . Conceput pentru a înlocui limbajele C și C++ , D folosește cuvintele cheie și . moduleimport
Constantele preprocesorului și macrocomenzile sunt folosite pentru a defini bucăți mici de cod.
// constantă #define BUFFER_SIZE ( 1024 ) // macro #define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )Fiecare constantă și fiecare macro este înlocuită cu definiția corespunzătoare. Macro-urile au parametri asemănători funcției și sunt folosite pentru a reduce supraîncărcarea apelurilor de funcții în cazurile în care cantitatea mică de cod pe care o apelează funcția este suficientă pentru a provoca o performanță vizibilă.
Exemplu. Definiția macro-ului max , care ia două argumente: a și b .
#define max( a, b ) ( (a) > (b) ? (a) : (b) )O macrocomandă este numită la fel ca orice funcție.
z = max ( x , y );După înlocuirea macrocomenzii, codul va arăta astfel:
z = ( ( ( x ) > ( y ) ? ( x ) : ( y ) );Cu toate acestea, alături de avantajele utilizării macrocomenzilor în limbajul C, de exemplu, pentru a defini tipuri de date generice sau instrumente de depanare, acestea reduc oarecum eficiența utilizării lor și pot duce chiar la erori.
De exemplu, dacă f și g sunt două funcții, apelul
z = max ( f (), g () );nu va evalua f() o dată și g() o dată și va pune cea mai mare valoare în z , așa cum v-ați aștepta. În schimb, una dintre funcții va fi evaluată de două ori. Dacă o funcție are efecte secundare, este probabil ca comportamentul ei să fie diferit de cel așteptat.
Macro-urile C pot fi asemenea funcțiilor, creând într-o oarecare măsură sintaxă nouă și pot fi, de asemenea, mărite cu text arbitrar (deși compilatorul C cere ca textul să fie în cod C fără erori sau formatat ca comentariu), dar au unele limitări precum structurile software. Macro-urile asemănătoare unei funcții, de exemplu, pot fi numite ca funcții „reale”, dar o macrocomandă nu poate fi transmisă unei alte funcții folosind un pointer, deoarece macro-ul în sine nu are nicio adresă.
Unele limbi moderne nu folosesc de obicei acest tip de metaprogramare folosind macrocomenzi ca completări ale șirurilor de caractere, bazându-se pe cablarea automată sau manuală a funcțiilor și metodelor, ci în schimb pe alte modalități de abstractizare, cum ar fi șabloanele , funcțiile generice sau polimorfismul parametric . În special, funcțiile inline unul dintre deficiențele majore ale macrocomenzilor în versiunile moderne de C și C++, deoarece o funcție inline oferă avantajul macrocomenzilor în reducerea supraîncărcării unui apel de funcție, dar adresa sa poate fi transmisă într-un pointer pentru indirect. apeluri sau utilizate ca parametru. La fel, problema evaluărilor multiple menționată mai sus în macro-ul maxim este irelevantă pentru funcțiile încorporate.
Puteți înlocui constantele #define cu enumerari și macrocomenzi cu funcții inline.
Operatorii # și ##Acești operatori sunt utilizați la crearea macrocomenzilor. Operatorul # înaintea unui parametru macro îl încadrează între ghilimele duble, de exemplu:
#define make_str( bar ) # bar printf ( make_str ( 42 ) );preprocesorul se convertește în:
printf ( "42" );Operatorul ## din macrocomandă concatenează două jetoane, de exemplu:
#define MakePosition( x ) x##X, x##Y, x##Width, x##Height int MakePosition ( Object );preprocesorul se convertește în:
int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; Descrierea oficială a macrosubstituțiilor1) Linia de control din următoarea formă forțează preprocesorul să înlocuiască identificatorul cu o secvență de jetoane în restul textului programului:
#define identificator token_sequenceÎn acest caz, caracterele cu spații albe de la începutul și de la sfârșitul secvenței de jetoane sunt eliminate. O linie #define repetată cu același identificator este considerată o eroare dacă secvențele de jetoane nu sunt identice (nepotrivirile în caracterele de spațiu alb nu contează).
2) Un șir de forma următoare, în care nu trebuie să existe caractere de spațiu alb între primul identificator și paranteza de deschidere, este o definiție macro cu parametri specificați de identificator-list.
#define identifier(list_of_identifiers) sequence_of_tokensCa și în prima formă, caracterele de spații albe de la începutul și sfârșitul secvenței de jetoane sunt eliminate, iar macro-ul poate fi redefinită numai cu aceeași listă de parametri de număr și nume și aceeași secvență de jetoane.
O linie de control ca aceasta îi spune preprocesorului să „uite” definiția dată identificatorului:
#undef identificatorAplicarea directivei #undef la un identificator nedefinit anterior nu este considerată o eroare.
{
Procesul de înlocuire este afectat de două semne speciale de operator.
}
Un semn de exclamare (!) marchează regulile responsabile pentru invocarea recursivă și definițiile.
Exemplu de extindere a macrocomenzii #define cat( x, y ) x ## yApelul macro „cat(var, 123)” va fi înlocuit cu „var123”. Cu toate acestea, apelarea „cat(cat(1, 2), 3)” nu va produce rezultatul dorit. Luați în considerare pașii preprocesorului:
0: pisica( pisica( 1, 2 ), 3 ) 1: pisica ( 1, 2 ) ## 3 2: pisica( 1, 2 )3Operația „##” a împiedicat extinderea corectă a argumentelor celui de-al doilea apel „pisica”. Rezultatul este următorul șir de jetoane:
pisica ( 1 , 2 ) 3unde „)3” este rezultatul concatenării ultimului simbol al primului argument cu primul simbol al celui de-al doilea argument, nu este un simbol valid.
Puteți specifica al doilea nivel macro după cum urmează:
#define xcat( x, y ) cat( x, y )Apelul „xcat(xcat(1, 2), 3)” va fi înlocuit cu „123”. Luați în considerare pașii preprocesorului:
0: xcat( xcat( 1, 2 ), 3 ) 1: pisică( xcat( 1, 2 ), 3 ) 2: pisica( pisica( 1, 2 ), 3 ) 3: pisica ( 1 ## 2, 3 ) 4: pisica (12, 3) 5:12##3 6:123Totul a mers bine, deoarece operatorul „##” nu a participat la extinderea macrocomenzii „xcat”.
Mulți analizoare statice nu sunt capabili să proceseze macro-urile corect, astfel încât calitatea analizei statice este redusă .
Constante generate automat de preprocesor:
Preprocesorul C oferă capacitatea de a compila cu condiții. Acest lucru permite posibilitatea unor versiuni diferite ale aceluiași cod. De obicei, această abordare este utilizată pentru a personaliza programul pentru platforma compilatorului, stare (codul depanat poate fi evidențiat în codul rezultat) sau capacitatea de a verifica conexiunea fișierului exact o dată.
În general, programatorul trebuie să folosească un construct precum:
# ifndef FOO_H # definește FOO_H ...( codul fișierului antet )... # endifAceastă „protecție macro” împiedică un fișier antet să fie dublu inclus prin verificarea existenței acelei macrocomenzi, care are același nume ca fișierul antet. Definiția macrocomenzii FOO_H apare atunci când fișierul antet este procesat pentru prima dată de preprocesor. Apoi, dacă acest fișier antet este re-inclus, FOO_H este deja definit, determinând preprocesorul să omite întregul text al acestui fișier antet.
Același lucru se poate face prin includerea următoarei directive în fișierul antet:
# pragma o datăCondițiile preprocesorului pot fi specificate în mai multe moduri, de exemplu:
# ifdef x ... #altfel ... # endifsau
# ifx ... #altfel ... # endifAceastă metodă este adesea folosită în fișierele antet de sistem pentru a testa diferite capabilități, a căror definiție poate varia în funcție de platformă; de exemplu, biblioteca Glibc folosește macrocomenzi de verificare a caracteristicilor pentru a verifica dacă sistemul de operare și hardware-ul le suportă corect (macromorile) menținând în același timp aceeași interfață de programare.
Majoritatea limbajelor de programare moderne nu profită de aceste caracteristici, bazându-se mai mult pe instrucțiunile condiționate tradiționale if...then...else..., lăsând compilatorului sarcina de a extrage cod inutil din programul compilat.
Vedeți digrafele și trigrafele în limbaje C/C++.
Preprocesorul prelucrează digrafele „ %:” (“ #”), “ %:%:” (“ ##”) și trigrafele “ ??=” (“ #”), “ ??/” (“ \”).
Preprocesorul consideră secvența „ %:%: ” ca fiind două jetoane atunci când procesează codul C și un simbol atunci când procesează codul C++.
limbaj de programare C | |
---|---|
Compilatoare |
|
Biblioteci | |
Particularități | |
Unii descendenți | |
C și alte limbi |
|
Categorie: limbaj de programare C |