Directivele limbajului C macros constante fișiere antet. Directivele preprocesorului și instrucțiunile compilatorului

preprocesor Scopul unui preprocesor este de a procesa textul sursă al unui program înainte de a-l compila. Preprocesarea include mai multe etape efectuate secvenţial. O anumită implementare poate combina mai multe etape, dar rezultatul trebuie să fie ca și cum ar fi fost efectuate în următoarea ordine:
  1. Toate desemnările dependente de sistem sunt recodificate în coduri standard.
  2. Fiecare pereche de caractere „\” și „sfârșit de linie”, împreună cu spațiile dintre ele, sunt eliminate și, astfel, următoarea linie a textului sursă este atașată la linia în care a fost localizată această pereche de caractere.
  3. Directivele de preprocesor și jetoanele sunt recunoscute în text și fiecare comentariu este înlocuit cu un singur caracter de spațiu alb.
  4. Directivele de preprocesor sunt executate și sunt efectuate substituții de macro.
  5. Secvențe de evadareîn constantele de caractere și șirurile de caractere sunt înlocuite cu echivalentele lor.
  6. Șirurile de caractere adiacente sunt concatenate, adică unite într-un șir.
  7. Fiecare jeton de preprocesor este convertit în text C.

Să explicăm ce se înțelege prin jetoane de preprocesor sau jetoane de preprocesor. Acestea includ constante de caractere, includ nume de fișiere, identificatori, semne de operație, numere de preprocesor, semne de punctuație, constante de șir și orice alte caractere decât spațiul.

Etapa de procesare a directivelor preprocesor. Când efectuați acest lucru, sunt posibile următoarele acțiuni:

  • înlocuirea identificatorilor cu secvențe de caractere pregătite în prealabil;
  • includerea de texte din fișierele specificate;
  • excluderea din program a unor părți individuale ale textului său, compilare condiționată;
  • substituție macro, adică înlocuirea denumirii cu text parametrizat generat de preprocesor ținând cont de argumente specifice.

Constante simbolice: #define

Dacă primul caracter dintr-o linie de program este #, atunci acea linie este Linie de comanda preprocesor (macroprocesor). Linie de comanda preprocesorul se termină cu un caracter de traducere la linie nouă. Dacă puneți un caracter backslash „\” imediat înainte de sfârșitul liniei, atunci Linie de comanda va continua la următoarea linie a programului.

Directiva #define, ca toate directivele de preprocesor, începe cu un caracter # în poziția cea mai din stânga. Poate apărea oriunde în fișierul sursă, iar definiția dată este valabilă de unde apare până la sfârșitul fișierului. Utilizăm în mod activ această directivă pentru a defini constante simboliceîn programele noastre exemplu, totuși, are aplicații mai largi, pe care le vom arăta în continuare.

Înlocuirea identificatorilor

#define șir de identificare

Înlocuiește fiecare apariție a identificatorului ABC din textul programului cu 100:

#undef identificator

Ignoră definiția anterioară pentru identificatorul ABC.

/* Exemple simple directive de preprocesor */ #define TWO 2 /* comentariile pot fi folosite */ #define MSG "Text 1.\ Text 1 continuat" /* backslash continuă definiția la următoarea linie */ #define FOUR TWO*TWO #define PX printf ( "X este egal cu %d.\n", x) #define FMT "X este egal cu %d.\n" int main() ( int x = DOI; PX; x = PATRU; printf(FMT,x ); printf(" %s\n",MSG("TWO: MSG\n");

Ca rezultat al executării exemplului nostru, vom avea.

Directive privind preprocesorul

De fiecare dată când compilatorul este pornit, este mai întâi lansat preprocesorul, care execută directive începând cu simbolul #. Când aceste comenzi sunt executate, este creat un fișier de cod sursă temporar cu care compilatorul lucrează deja.

directiva #include

Linia
#include „nume fișier”

Înlocuiește această linie cu conținutul complet al numelui fișierului. fişier. Dacă calea nu este specificată, atunci fișierul este căutat mai întâi în directorul fișierului sursă, apoi în directorul specificat în opțiunile compilatorului (de obicei, directorul Include).

Linia #include<имя файла>

Caută un fișier numai în directorul specificat în opțiunile compilatorului.

#define directivă

#define șir de caractere identificator

Înlocuiește toate aparițiile ulterioare ale identificatorului cu un șir de caractere. Exemplu:

#define A_NUMBER 100

int n=A_NUMĂR;

lui n i se va atribui valoarea 100

#define poate fi folosit și pentru a defini macrocomenzi, de exemplu:

#definiți SWAP(a,b) temp=(a);(a)=(b);(b)=temp

Mai multe detalii despre #define (și macrocomenzi în special) vor fi într-un articol separat.

directiva #undef

#undef identificator

Ignoră determinarea ID-ului pre-procesorului.

Directive #if #else #endif

expresie #if

Verifică dacă expresia este adevărată și dacă este adevărată, apoi execută toate liniile ulterioare până la directiva #endif.

Tip constructie:

expresie #if

#endif Verifică expresia, dacă este adevărată atunci liniile dintre #if și #else sunt executate și dacă este falsă atunci între #else și #endif.

Directive #ifdef #ifndef

#ifdef identificator

Verifică dacă identificatorul este definit în preprocesor în acest moment(prin directiva #define) și dacă este definit, apoi execută toate liniile ulterioare până la directiva #endif.

#ifndef identificator

Dimpotrivă, execută toate liniile ulterioare până la directiva #endif dacă identificatorul nu este definit în prezent în preprocesor.

directiva #error

#eroare- mesaj de eroare. Oprește compilatorul și afișează un mesaj de eroare. De exemplu:

#ifndef smth_important

#eroare ceva important nu este definit

Compilatorul va produce ceva de genul:

Fatal F1003 file.cpp 2: Directiva de eroare: ceva important nu este definit

*** 1 erori în compilare ***

directivă #line

Directivă
#line constantă „nume fișier” Determină compilatorul să presupună că constanta specifică următorul număr de linie al fișierului sursă, iar fișierul de intrare curent este denumit un identificator. Dacă identificatorul lipsește, numele fișierului reținut nu este schimbat.

directivă #pragma

#pragma este o directivă de preprocesor care implementează capabilitățile compilatorului. Aceste caracteristici pot fi legate de tipul de compilator. Diferite tipuri de compilatoare pot suporta directive diferite. Forma generală directive:

Comanda compilatorului #pragma

De exemplu:

#pragma mesaj ("mesaj")- doar produce un mesaj la compilare.

Domeniul de aplicare

Funcțiile și variabilele externe care alcătuiesc un program C nu trebuie să fie compilate împreună de fiecare dată. Textul sursă poate fi stocat în mai multe fișiere. Programele compilate anterior pot fi încărcate din biblioteci. În acest sens, se ridică următoarele întrebări:

Cum se scriu declarații astfel încât variabilele utilizate să fie declarate corect pe parcursul compilării?

În ce ordine trebuie plasate declarațiile astfel încât în ​​timpul încărcării toate părțile programului să fie conectate corect?

Cum îmi pot organiza anunțurile astfel încât să aibă o singură copie?

Cum se inițializează variabilele externe?

Să începem prin a împărți programul calculatorului în mai multe fișiere. Desigur, acest program este prea mic pentru a merita împărțit în fișiere, dar împărțirea programului nostru va ajuta la demonstrarea problemelor care apar în programele mari.

Domeniul de aplicare nume este considerat a fi partea din program în care acest nume poate fi folosit. Pentru variabilele automate declarate la începutul unei funcții, domeniul de aplicare este funcția în care sunt declarate. Variabile locale diferite funcții, cu toate acestea, având aceleași nume nu sunt în niciun fel legate între ele. Aceeași afirmație este valabilă pentru parametrii funcției, care sunt de fapt variabile locale.

Sfera de aplicare a unei variabile sau funcție externe se extinde de la punctul din program în care este declarată până la sfârșitul fișierului care trebuie compilat. De exemplu, dacă principal, sp, val, ApăsaţiȘi pop sunt definite într-un singur fișier în ordinea specificată, adică

Main() (...) int sp = 0;double val; void push(double f) (...) double pop(void) (...)

apoi la variabile spȘi val se poate adresa de la ApăsaţiȘi pop doar după numele lor; nu sunt necesare anunțuri suplimentare pentru aceasta. Rețineți că în principal aceste nume nu sunt vizibile la fel de bine ca ele însele ApăsaţiȘi pop.

Totuși, dacă o variabilă externă trebuie referită înainte de a fi definită sau dacă este definită într-un alt fișier, atunci declarația ei trebuie marcată cu cuvântul extern.

Este important să distingem anunţ variabilă externă din ea definiții. O declarație declară proprietățile unei variabile (în primul rând tipul acesteia), iar o definiție îi alocă și memorie. Dacă liniile

Int sp;val dublu;

situate în afara tuturor funcțiilor, apoi acestea a determina variabile externe spȘi val, adică alocă memorie pentru ei și servesc, de asemenea, drept declarații pentru restul fișierului sursă. Și aici sunt rândurile

Extern int sp;extern double val;

anunta pentru restul dosarului, asta sp- variabila de tip int, A val- tipul matricei dubla(a cărui dimensiune este definită în altă parte); în acest caz, nu este creată nici variabila, nici matricea și nu le este alocată nicio memorie.

Pentru întregul set de fișiere care alcătuiesc programul sursă, pentru fiecare variabilă externă trebuie să existe una singură definiție; alte fișiere trebuie să aibă o declarație în ele pentru a accesa variabila externă extern. (Totuși, anunțul extern poate fi plasat și în fișierul care conține definiția.) Definițiile de matrice trebuie să indice dimensiunile lor, care în declarații extern nu este necesar. Puteți inițializa o variabilă externă numai în definiție. Deși este puțin probabil să ne organizăm programul în acest fel, vom defini push și pop într-un fișier, iar val și sp în altul, unde le inițializam. În acest caz, pentru a stabili conexiuni veți avea nevoie de următoarele definiții și declarații:

În dosarul 1: extern int sp;extern double val; void push(double f) (...) double pop(void) (...) În fișierul 2: int sp = 0;val dublu;

Pentru că reclamele extern sunt la început fisier1 iar în afara definițiilor funcțiilor, efectul lor se extinde la toate funcțiile și un set de declarații este suficient pentru orice fisier1. Aceeași organizație extern-declarations este necesar și în cazul în care programul constă dintr-un singur fișier, dar definițiile sp și val sunt localizate după utilizarea lor.

Universitatea Națională Deschisă „INTUIT”: www.intuit.ru Nina Kalinina, Nina Kostyukova Curs 11. Preprocesor limbaj C

Informații generale

Un mediu integrat pentru pregătirea programelor C sau a unui compilator de limbaj include un preprocesor ca componentă obligatorie. Scopul unui preprocesor este de a procesa textul sursă al unui program înainte de a-l compila. Preprocesarea include mai multe etape efectuate secvenţial. O anumită implementare poate combina mai multe etape, dar rezultatul trebuie să fie ca și cum ar fi fost efectuate în următoarea ordine:

1. Toate denumirile dependente de sistem sunt recodificate în coduri standard.

2. Fiecare pereche de personaje"\ " și "sfârșitul rândului" împreună cu spațiile dintre ele sunt eliminate și, prin urmare, următoarea linie a textului sursă este atașată la rândul în care a fost localizată această pereche de caractere.

3. Directivele de preprocesor și jetoanele sunt recunoscute în text și fiecare comentariu este înlocuit cu un singur caracter de spațiu alb.

4. Directivele de preprocesor sunt executate și sunt efectuate substituții de macro.

5. Secvențe de evadareîn constantele de caractere și șirurile de caractere sunt înlocuite cu echivalentele lor.

6. Șirurile de caractere adiacente sunt concatenate, adică unite într-un șir.

7. Fiecare jeton de preprocesor este convertit în text C.

Să explicăm ce se înțelege prin jetoane de preprocesor sau jetoane de preprocesor. Acestea includ constante de caractere, includ nume de fișiere, identificatori, semne de operație, numere de preprocesor, semne de punctuație, constante de șir și orice alte caractere decât spațiul.

Etapa de procesare a directivelor preprocesor. La efectuarea acestuia sunt posibile următoarele acțiuni: înlocuirea identificatorilor cu secvențe de caractere pregătite în prealabil; includerea textelor din fișierele specificate în program; excluderea din program a unor părți individuale ale textului său, compilare condiționată;

substituție macro, adică înlocuirea denumirii cu text parametrizat generat de preprocesor ținând cont de argumente specifice.

Constante simbolice: #define

Dacă primul caracter dintr-o linie de program este #, atunci acea linie este Linie de comanda preprocesor (macroprocesor). Linie de comanda preprocesorul se termină cu un caracter newline. Dacă puneți un caracter backslash „\” imediat înainte de sfârșitul liniei, atunci Linie de comanda va continua la următoarea linie a programului.

Directiva #define, ca toate directivele de preprocesor, începe cu un caracter # în poziția cea mai din stânga. Poate apărea oriunde în fișierul sursă, iar definiția dată este valabilă de unde apare până la sfârșitul fișierului. Utilizăm în mod activ această directivă pentru a defini constante simboliceîn programele noastre exemplu, dar are aplicații mai largi, pe care le vom arăta în continuare.

Înlocuirea identificatorilor

#define șir de identificare

Înlocuiește fiecare apariție a identificatorului ABC în textul programului cu 100:

#undef identificator

Preprocesor limbaj C

Ignoră definiția anterioară pentru identificatorul ABC.

/* Exemple simple ale unei directive de preprocesor */ #define TWO 2 /* pot fi folosite comentarii */ #define MSG "Text 1.\

Textul a continuat 1"

/* backslash continuă definiția la rândul următor */ #define FOUR TWO*TWO

#define PX printf("X este egal cu %d.\n", x) #define FMT "X este egal cu %d.\n"

int x = DOI; PX;

x = PATRU; printf(FMT,x); printf("%s\n",MSG); printf("DOI:MSG\n"); returneaza DOUA;

ÎN Ca rezultat al rulării exemplului nostru, vom avea:

X este 2 X este 4

Textul 1. Continuarea textului 1 DOI: MSG

Să ne dăm seama ce s-a întâmplat. Operator

se transformă în

Apoi operatorul

se transformă în

printf("X este egal cu %d.\n",x);

deoarece s-a făcut o înlocuire completă. Acum vedem că o definiție macro poate reprezenta orice șir, chiar și o întreagă expresie C. Rețineți că acesta este un șir constant. PX va imprima doar variabila numită x .

ÎN rândul următor se face urmatoarele: x = PATRU;

se întoarce

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Preprocesor limbaj C

se transformă în

și acolo se termină totul. Înmulțirea efectivă are loc nu în timp ce preprocesorul rulează sau în timpul compilării, ci întotdeauna, fără excepție, în timp ce programul rulează. Preprocesorul nu efectuează calcule. El face doar înlocuirile sugerate foarte precis. Rețineți că o definiție macro poate include și alte definiții. Unele compilatoare nu acceptă această proprietate de imbricare. Pe linia următoare

se transformă în

printf("X este egal cu %d.\n",x)

când FMT este înlocuit linia corespunzătoare. Această abordare poate fi foarte convenabilă dacă există un șir lung pe care îl folosim de mai multe ori. În următoarea linie a programului, MSG este înlocuit cu linia corespunzătoare. Ghilimele fac din șirul de înlocuire o constantă șir de caractere. Pe măsură ce programul își preia conținutul, acest șir va fi stocat într-o matrice terminată cu nul. Asa de,

#define HAL „X” definește constantă simbolică, A

#define HAR „X” definește șirul de caractere X\0

De obicei, atunci când un preprocesor întâlnește una dintre definițiile macro într-un program, le înlocuiește foarte precis cu un șir de înlocuire echivalent. Dacă această linie conține și definiții macro, acestea sunt și ele înlocuite. Singura excepție de la înlocuire este o definiție macro în ghilimele duble. De aceea

printf("DOI: MSG");

literalmente imprimă TWO:MSG în loc să imprime următorul text:

2: „Textul 1.

Textul a continuat 1"

Dacă trebuie să tipărim acest text, putem folosi operatorul

printf("%d: %s\n",TWO,MSG);

pentru că aici definițiile macro sunt în afara ghilimelelor.

Când să utilizați constante simbolice? Probabil că ar trebui să le folosim pentru majoritatea numerelor. Dacă numărul este o constantă folosită în calcule, atunci nume simbolicîi face sensul mai clar. Dacă numărul este dimensiunea matricei, atunci nume simbolic facilitează modificarea programului atunci când lucrați cu o matrice mare. Dacă numărul este codul de sistem, să spunem pentru simbolul EOF , atunci reprezentarea simbolică face programul mai portabil. Se modifică doar definiția EOF. Sens mnemonic, ușurință de schimbare, portabilitate: face totul

constante simbolice demn de atentie!

Folosind argumente cu #define

Pentru a evita erorile la evaluarea expresiilor, parametrilor definiții macro trebuie incheiat

în paranteze.

#define identifier1 (identificator2, . . .) șir

#define abs(A) (((A) > 0)?(A) : -(A))

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Preprocesor limbaj C

Fiecare apariție a expresiei abs(arg) în textul programului este înlocuită cu

((arg) > 0) ? (arg): -(arg),

în care parametrul de definire macro A este înlocuit cu arg .

#define nmem(P,N)\

(P) -> p_mem[N].u_long

Caracterul \ continuă definiția macro până la a doua linie. Această macrodefiniție reduce complexitatea expresiei care descrie o serie de uniuni în cadrul unei structuri.

O definiție macro cu argumente este foarte asemănătoare cu o funcție, deoarece argumentele sale sunt incluse în paranteze:

/* definiție macro cu argumente */ #define SQUARE(x) x*x

#define PR(x) printf("x este egal cu %d.\n", x)

PR(PĂRAT(x+2));

PR(100/PĂTRAT(2));

PR(PĂTRAT(++x)); returnează 0;

Oriunde apare definiția macro SQUARE(x) în programul nostru, aceasta este înlocuită cu x*x. Spre deosebire de exemplele noastre anterioare, atunci când utilizați acest lucru definiții macro suntem complet liberi să folosim alte simboluri decât x . Într-o definiție de macro, „x” este înlocuit cu simbolul folosit în apelul macro de program. Prin urmare, definiția macro SQUARE(2) este înlocuită cu 2*2 . Deci x acționează ca un argument. In orice caz, argument macro nu funcționează - exact la fel ca un argument de funcție. Iată rezultatele rulării programului:

z este egal cu 16. z este egal cu 4.

SQUARE(x) este 16. SQUARE(x+2) este 14. 100/SQUARE(2) este 100. SQUARE(++x) este 30.

Primele două rânduri sunt evidente. Rețineți că, chiar și între ghilimele duble din definiția PR, variabila este înlocuită cu argumentul corespunzător. Toate argumentele din această definiție sunt înlocuite. Luați în considerare a treia linie:

Devine următoarea linie:

printf("SQUARE(x) este egal cu %d.\n", SQUARE(x));

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Preprocesor limbaj C

după prima etapă de macroexpansiune. Cel de-al doilea SQUARE(x) se extinde pentru a deveni x*x , dar primul rămâne neschimbat deoarece este acum între ghilimele în instrucțiunea programului și, astfel, este protejat de extinderea ulterioară. În cele din urmă, linia de program conține

printf("SQUARE(x) este egal cu %d.\n",x*x);

și imprimeuri

SQUARE(x) este egal cu x*x.

Dacă definiția macro include un argument cu ghilimele duble, atunci argumentul va fi înlocuit cu șirul din apelul macro. Dar după aceea nu este mai extins, chiar dacă șirul este o altă definiție macro. În exemplul nostru, variabila x a devenit și rămâne definiția macro SQUARE(x). Amintiți-vă că x=4. Aceasta sugerează că SQUARE(x+2) va fi egal cu 6*6 sau 36 . Dar rezultatul tipărit spune că rezultatul este numărul 14. Motivul pentru acest rezultat este că preprocesorul nu face calculele. Înlocuiește doar șirul. Oriunde definiția noastră indică x , preprocesorul va înlocui șirul x+2 .

Prin urmare,

x*x devine x+2*x+2

Dacă x este egal cu 4, atunci se dovedește

4+2*4+2=4+8+2=14

Un apel de funcție transmite valoarea argumentului funcției în perioada de graţie programe. Un apel macro transmite un șir de argumente unui program înainte de a fi compilat.

Definiție macro sau funcție?

! Multe probleme pot fi rezolvate folosind o definiție macro cu argumente sau o funcție. Pe care ar trebui să-l folosești? Nu există reguli stricte în acest sens, dar există câteva considerații.

Definiții macro ar trebui să fie folosit ca trucuri, mai degrabă decât ca funcții normale. Ele pot avea efecte secundare nedorite. Unele compilatoare limitează definiții macroîntr-o singură linie și pare mai bine să respectați o astfel de restricție, chiar dacă compilatorul dvs. nu o face.

Alegere definiții macro duce la o creștere a memoriei, iar alegerea funcției duce la o creștere a timpului de rulare a programului. Definiția macro creează un cod de linie, adică primim declarația în program. Dacă definiția macro este aplicată de 20 de ori, atunci 20 de linii de cod vor fi inserate în program. Dacă folosim o funcție de 20 de ori, atunci vom avea o singură copie a instrucțiunilor funcției. Cu toate acestea, controlul programului ar trebui transferat acolo unde este localizată funcția și apoi revenit la program de apelare, iar acest lucru va dura mai mult timp decât atunci când lucrați cu coduri de linie. Asa ca gandeste-te ce sa alegi!

Avantajul definițiilor macro este că atunci când le folosim nu trebuie să ne facem griji cu privire la tipurile de variabile deoarece definiții macro se ocupă mai degrabă de șiruri de caractere decât de valori reale. Deci, definiția noastră macro SQUARE(x) poate fi folosită la fel de bine cu variabile de tip int sau float .

Să ne amintim!

1. Nu există spații în definiția macro, dar ele pot apărea în șirul de înlocuire. Preprocesorul presupune că definiția macro se termină pe primul spațiu, astfel încât totul după spațiu rămâne în șirul de înlocuire.

2. Folosiți paranteze pentru fiecare argument și pentru întreaga definiție. Acest lucru asigură că elementele sunt grupate corect în expresie.

3. Numele funcțiilor macro trebuie scrise cu majuscule. Această convenție nu se aplică la fel de larg ca convenția de scriere cu majuscule pentru constantele macro. Utilizarea lor va preveni posibilele efecte secundare

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Preprocesor limbaj C

definiții macro.

Să presupunem că am dezvoltat mai multe funcții macro pe cont propriu. Dacă scriem program nou, nu ar trebui să le depășim. Trebuie să utilizați directiva #include.

Includerea unui fișier: #include

Lista denumirilor de fișiere antet pentru lucrul cu bibliotecile compilatorului este aprobată de standardul de limbă. Mai jos sunt numele acestor fișiere, precum și informatie scurta despre acele descrieri și definiții care sunt incluse în acestea. Majoritatea descrierilor sunt prototipuri ale funcțiilor standard și sunt definite în principal constante, de exemplu EOF, necesare pentru lucrul cu funcțiile de bibliotecă.

assert.h - diagnosticare program

ctype.h - conversia și verificarea caracterelor

errno.h - verificarea erorilor

float.h - lucrul cu date reale

limits.h - limite de date întregi

locale.h - suport pentru mediul național

math.h - calcule matematice

setjump.h - capabilități de sărituri non-locale

semnal.h - gestionarea excepţiilor

stdarg.h - suport pentru un număr variabil de parametri stddef.h - definiții suplimentare

stdio.h - facilităţi de intrare/ieşire

stdlib.h - funcții de uz general (lucru cu memorie) șir.h - lucru cu șiruri de caractere

time.h - definirea datelor și orelor

În implementări specifice, numărul și numele fișierelor antet pot fi diferite. De exemplu, compilatoarele pentru MS-DOS folosesc în mod activ fișierele de antet mem.h , alloc.h , conio.h , dos.h și altele. Utilizează compilatoarele Turbo C și Borland C++ fișier antet grafică.h .

Linie de comanda#include poate apărea oriunde într-un program, dar de obicei toate includerile sunt plasate la începutul fișierului sursă.

#include<имя_файла>

#include

Procesorul înlocuiește această linie cu conținutul fișierului math.h. Parantezele unghiulare înseamnă că fișierul math.h va fi luat dintr-un director standard (de obicei /usr/include).

#include „nume fișier”

Preprocesorul înlocuiește această linie cu conținutul fișierului ABC. Deoarece fișierul este cuprins între ghilimele, căutarea topografică este efectuată în directorul curent (care conține fișierul sursă principal). Dacă se află în directorul curent a acestui dosar nu, căutarea topo se efectuează în directoarele specificate de numele căii în opțiunea -l a preprocesorului. Dacă nu există niciun fișier acolo, atunci directorul standard este examinat.

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Preprocesor limbaj C

ÎN sistem de operare Parantezele unghiulare UNIX îi spun preprocesorului că fișierul trebuie căutat într-unul sau mai multe standarde directoarele de sistem. Ghilimele îi spun să caute mai întâi în directorul dvs. sau într-un alt director dacă îl specificați printr-un nume de fișier, apoi căutați în locurile standard.

ÎN În implementări specifice, numărul și numele fișierelor antet pot fi diferite:

#include „/user/1/my.h” caută în directorul /user/1

Într-un sistem tipic cu microprocesor, cele două forme sunt sinonime, iar preprocesorul caută discul specificat.

#include căutările „stdio.h” pe discul standard

#include căutări pe un disc standard

#include căutările „a:stdio.h” pe disc a

Prin convenție, sufixul .h este utilizat pentru fișierele antet, adică. fișiere cu informații care se află la începutul programului. Fișierele antet constau de obicei din instrucțiuni de preprocesor.

Unele fișiere sunt incluse în sistem, de exemplu, stdio.h, dar vă puteți crea propriul fișier.

Mulți programatori își dezvoltă propriile fișiere de antet standard pentru a le utiliza în programele lor.

Compilare condiționată

Liniile de comandă preprocesoarele sunt folosite pentru a compila condiționat diverse părți ale textului sursă în funcție de condițiile externe.

#if expresie_constantă

Adevărat dacă expresia constantă ABC + 3 nu este zero.

#ifdef identificator

adevărat dacă identificatorul ABC a fost definit anterior cu comanda #define.

#ifndef identificator

adevărat dacă identificatorul ABC nu este definit în prezent.

Dacă testele precedente #if , #ifdef sau #ifndef sunt evaluate la True, atunci liniile #else până la #endif sunt ignorate în timpul compilării.

Comanda #endif marchează sfârșitul compilării condiționate.

fprintf(stderr, "locație: x = %d\n", x); #endif

Directive auxiliare

Numărul liniei și numele fișierului

#line constantă întreagă „nume fișier”

Preprocesorul schimbă numărul linia curentăși numele fișierului care se compila. Numele fișierului poate fi omis.

Unul dintre scopurile utilizării compilației condiționate este de a face un program mai portabil. Schimbând câteva definiții cheie la începutul fișierului, putem seta sensuri diferiteși includ diverse fișiere pentru diferite sisteme.

#definiți N 3/*definiți o constantă */

#linia 55 „fișier.c”

Reacția la erori

#eroare secvență de jetoane

Prelucrarea directivei are ca rezultat emiterea unui mesaj de diagnosticare în forma definită de secvența de jetoane. Utilizarea acestei directive împreună cu comenzile condiționale ale preprocesorului.

În viitor, îi puteți verifica valoarea și puteți afișa un mesaj dacă NAME are o valoare diferită:

#error NAME trebuie să aibă 15!

Mesajul va arăta astfel:

eroare<имя_файла><номер_строки >;

directivă de eroare: NAME trebuie să fie egal cu 15!

Directiva goală

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

FIŞIER__

Preprocesor limbaj C

Utilizarea acestei directive nu provoacă nicio acțiune.

Această directivă definește acțiuni care depind de implementarea specifică a compilatorului. De exemplu, unele compilatoare includ o versiune a acestei directive pentru a notifica compilatorul despre prezența comenzilor din limbajul de asamblare în textul programului. Capacitățile comenzii #pragma pot fi variate. Nu există un standard pentru ei. Dacă un anumit preprocesor întâlnește o pragma care îi este necunoscută, pur și simplu o ignoră ca o directivă goală. Unele implementări includ o pragma.

#pragma pack(n) , unde n= 1 ,2 ,4 . Pragmapack vă permite să influențați împachetarea elementelor adiacente în structuri și uniuni (vezi Lectura 14).

Acordul ar putea fi astfel:

pack(1) - aliniază elementele la granițele octeților;

pack(2) - alinierea elementelor la granițele cuvintelor;

pack(4) - alinierea elementelor de-a lungul limitelor cuvintelor duble;

Unele compilatoare includ pragma care vă permit să schimbați modul în care parametrii sunt transferați la funcții, ordinea în care parametrii sunt împinși în stivă și așa mai departe.

Nume macro încorporate

Există macronume încorporate (predefinite) disponibile pentru preprocesor în timpul procesării. Acestea vă permit să obțineți următoarele informații:

DATE__ - un șir de caractere în formatul: „numărul lunii an”, care definește data de începere a procesării fișierului sursă. De exemplu, după preprocesarea textului programului efectuată la 29 ianuarie 2005, operatorul

printf(__DATA__);

va deveni așa

printf("%s", "29 ianuarie 2005");

LINE__ - constantă zecimală - numărul liniei curente procesate a fișierului cu programul NaCi. Se presupune că numărul primei linii a fișierului sursă este 1;

FILE__ - șir de caractere - numele fișierului compilat. Numele se schimbă ori de câte ori preprocesorul întâlnește o directivă #include care specifică numele altui fișier. Când includerile de fișiere cu comanda #include sunt finalizate, valoarea anterioară a macronumelui este restaurată

TIME__ - un șir de caractere de forma „ore:minute:secunde”, care definește timpul în care preprocesorul începe procesarea fișierului sursă;

STDC__ este o constantă egală cu 1 dacă compilatorul funcționează în conformitate cu standardul ANSI. În caz contrar, valoarea micronumelui __STDC__ este nedefinită. Standard de limbaj C presupune că prezența numelui __STDC__ este definită de implementare deoarece macro-ul __STDC__ este o caracteristică a standardului. În implementările specifice, setul de nume predefinite este mult mai larg. Pentru informații mai complete despre numele preprocesoarelor predefinite, consultați documentația pentru compilatorul dvs. specific.

Atenţie! Dacă vedeți o eroare pe site-ul nostru, evidențiați-o și apăsați Ctrl+Enter.

© Universitatea Națională Deschisă „INTUIT”, 2012 | www.intuit.ru

www.intuit.ru/studies/professional_skill_improvements/1747/courses/43/print_lecture/663

Introducere

Preprocesorul în limbajul C este un macroprocesor folosit pentru a procesa fișierul sursă în faza zero a compilației. Compilatorul C în sine apelează preprocesorul, dar preprocesorul poate fi, de asemenea, apelat autonom. Directivele de preprocesor sunt instrucțiuni scrise în codul sursă al unui program C și destinate a fi executate de preprocesorul limbajului C.

Directivele de preprocesor sunt de obicei folosite pentru a face programele sursă mai ușor de modificat și mai independente de caracteristicile diferitelor implementări ale compilatorului C, computere și medii de operare. Directivele de preprocesor vă permit să înlocuiți lexemele din textul programului cu anumite valori, să introduceți conținutul unui alt fișier sursă în fișierul sursă, să interziceți compilarea unei părți a fișierului sursă etc. Preprocesorul C recunoaște următoarele directive:

#defini #altfel #dacă #ifndef #linia
#elif #endif #ifdef #include #undef

Simbolul # trebuie să fie primul din rândul care conține directiva în SP MSC versiunea 4. În SP MSC versiunea 5 și SP TS, acesta poate fi precedat de caractere cu spații albe. Atât în ​​SP MSC, cât și în SP TS, sunt permise caractere cu spații albe între simbolul # și prima literă a directivei.

Unele directive pot conține argumente. Directivele pot fi scrise oriunde în fișierul sursă, dar efectul lor se extinde numai de la punctul din program în care sunt scrise până la sfârșitul fișierului sursă.

Instrucțiunile compilatorului, sau pragma, sunt instrucțiuni scrise în codul sursă al unui program care sunt concepute pentru a controla ceea ce face compilatorul C în anumite situații. Setul de instrucțiuni ale compilatorului și semnificația lor diferă pentru diferitele compilatoare în limbaj C, astfel încât Secțiunea 7.8 descrie doar sintaxa generală a instrucțiunilor compilatorului.

În sistemele de programare luate în considerare, este posibil să se obțină textul programului intermediar după rularea preprocesorului, înainte de începerea compilării propriu-zise. Acest fișier a fost deja macrosubstituit și toate liniile care conțin directivele #define și #undef au fost înlocuite cu linii goale. Liniile #include sunt înlocuite cu conținutul fișierelor include corespunzătoare. Directivele de compilare condiționată #if, #elif, #else, #ifdef, #ifndef, #endif au fost procesate și liniile care le conțineau au fost înlocuite cu șiruri goale. Linii goale au fost de asemenea înlocuite fragmente din textul sursă excluse în timpul procesului de compilare condiționată. În plus, acest fișier conține linii precum următoarele:

#["nume de fișier"]

care corespund punctelor în care numărul de linie curent și/sau numărul de fișier se modifică conform directivelor #line sau #include.

Constante și macrocomenzi numite

Directiva #define este folosită de obicei pentru a înlocui constantele utilizate în mod obișnuit, cuvintele cheie, operatorii și expresiile într-un program cu identificatori semnificativi. Identificatorii care înlocuiesc constante numerice sau text sau o secvență arbitrară de caractere se numesc constante numite. Identificatorii care reprezintă o anumită secvență de acțiuni specificate de operatorii sau expresiile limbajului C se numesc macrodefiniții. Definițiile macro pot avea argumente. Apelarea unei definiții macro într-un program se numește apel macro.

În limbajul C, este o practică obișnuită să scrieți identificatori pentru constantele numite și definițiile macro în majuscule pentru a le distinge de numele variabilelor și ale funcțiilor. Aceasta, însă, nu este o cerință a limbajului C.

Directiva #undef suprascrie definiția curentă a unei constante numite. Doar atunci când definiția este nedefinită, unei constante numite i se poate atribui o altă valoare. Cu toate acestea, repetarea unei definiții de mai multe ori cu aceeași valoare nu este considerată o eroare.

O definiție macro este similară ca sintaxă cu o definiție de funcție. Cu toate acestea, înlocuirea unui apel de funcție cu un apel de macro poate îmbunătăți viteza de execuție a programului, deoarece definiția macro nu necesită generarea unei secvențe de apelare, care durează relativ mare vreme(împingerea argumentelor pe stivă, transferarea controlului etc.). Pe de altă parte, utilizarea definițiilor macro de mai multe ori într-un program poate necesita semnificativ mai multa memorie decât apelurile de funcție (codul pentru o funcție este generat o dată, iar pentru o definiție macro - de câte ori există apeluri macro în program).

Este posibil să specificați definiții ale constantelor numite nu numai în textul sursă, ci și pe linia de comandă de compilare.

Există un număr de identificatori predefiniti care nu pot fi utilizați ca identificatori în directivele #define și #undef. Ele sunt discutate în secțiunea 7.9 „Pseudo-variabile”.

#define directivă

Sintaxă:

Directiva #define înlocuiește toate aparițiile din fișierul sursă cu următoarele din directivă. Acest proces se numește macrosubstituție și este înlocuit doar dacă reprezintă un singur token. De exemplu, dacă face parte dintr-un șir sau un identificator mai lung, nu este înlocuit. Dacă este urmată, directiva definește o definiție macro cu argumente.

este un set de simboluri, cum ar fi cuvinte cheie, constante, identificatori sau expresii. Unul sau mai multe caractere de spații albe trebuie separate de (sau de parametrii aflați în paranteză). Dacă textul nu se încadrează pe o linie, acesta poate fi continuat pe linia următoare; Pentru a face acest lucru, tastați o bară oblică inversă la sfârșitul liniei și urmați-o imediat apăsând tasta ENTER.

poate fi omis. În acest caz, toate instanțele vor fi eliminate din codul sursă al programului. Cu toate acestea, el însuși este tratat așa cum este definit și, atunci când este testat cu directiva #if, dă valoarea 1 (vezi secțiunea 7.4.1).

Dacă este specificat, conține unul sau mai mulți identificatori, separați prin virgule. Identificatorii din listă trebuie să fie diferiți unul de celălalt. Sfera lor este limitată de macrodefiniția în care sunt definite. Lista trebuie inclusă între paranteze. Numele parametrilor formali din marchează pozițiile în care ar trebui înlocuite argumentele reale ale apelului macro. Fiecare nume formal de parametru poate apărea de un număr arbitrar de ori.

Într-un apel macro, o listă de argumente reale corespunzătoare parametrilor formali de la este scrisă în paranteze. modificat prin înlocuirea fiecărui parametru formal cu argumentul actual corespunzător. Listele de argumente efective și parametri formali trebuie să conțină același număr de elemente.

Notă. Nu confundați înlocuirea argumentelor într-o definiție macro cu transmiterea argumentelor unei funcții. Înlocuirea în preprocesor este de natură pur textuală. Nu se efectuează calcule sau conversii de tip.

S-a spus deja mai sus că o definiție macro poate conține mai mult de o apariție a unui parametru formal dat. Dacă un parametru formal este reprezentat de o expresie cu efect secundar, atunci această expresie va fi evaluată de mai multe ori, iar odată cu ea va apărea de fiecare dată un efect secundar. Rezultatul execuției în acest caz poate fi eronat.

Numele altor definiții macro sau constante pot fi imbricate în cadrul directivei #define. Ele sunt extinse doar atunci când acesta este extins, și nu atunci când este definit cu o directivă #define. Acest lucru trebuie luat în considerare, în special, atunci când constantele cu nume imbricate și definițiile macro interacționează cu directiva #undef: în momentul în care textul care le conține este extins, acestea pot fi deja anulate de directiva #undef.

După ce se efectuează înlocuirea macro, șirul rezultat este scanat din nou pentru a căuta alte nume constante și definiții macro. La vizualizarea din nou, numele înlocuirii macro efectuate anterior nu este luat în considerare. Prin urmare directiva

nu va face bucla preprocesorului.

/* exemplu 1 */

#define WIDTH 80

#define LUNGIME (LAȚime + 10)

/* exemplu 2 */

#define FILEMESSAGE „Încercarea de a crea un fișier\

a eșuat din cauza lipsei spatiu pe disc"

/* exemplu 3 */

#definiți registrul REG1

#definiți registrul REG2

/* exemplu 4 */

#definiți MAX(x, y)((x)>(y)) ? (X y)

/* exemplu 5 */

#define MULT(a, b) ((a)*(b))

În primul exemplu, identificatorul WIDTH este definit ca o constantă întreagă cu o valoare de 80, iar identificatorul LENGTH este definit ca text (WIDTH + 10). Fiecare apariție a identificatorului LENGTH în fișierul sursă va fi înlocuită cu textul (WIDTH + 10), care, după extinderea identificatorului WIDTH, va deveni expresia (80 + 10). Parantezele din jurul textului (WIDTH + 10) evită erorile în afirmații precum următoarele:

var = LUNGIME * 20;

După procesarea de către preprocesor, operatorul va lua forma:

var = (80 + 10)* 20;

Valoarea atribuită lui var este 1800. Fără paranteze în definiția macro, instrucțiunea ar arăta astfel:

var = 80 + 10*20;

Valoarea lui var ar fi 280 deoarece operația de înmulțire are prioritate mai mare decât operația de adunare.

Al doilea exemplu definește identificatorul FILEMESSAGE. Definiția sa continuă până la a doua linie folosind caracterul backslash imediat înainte de a apăsa tasta ENTER.

În al treilea exemplu, sunt definiți trei identificatori, REG1, REG2, REG3. Identificatorii REG1 și REG2 sunt definiți ca cuvinte cheie de registru. Definiția REG3 este omisă și astfel orice apariție a REG3 va fi eliminată din fișierul sursă. Secțiunea 7.4.1 oferă un exemplu care arată cum aceste directive pot fi utilizate pentru a seta clasa de memorie de registru la cele mai importante variabile ale programului.

Al patrulea exemplu definește macrodefiniția lui MAX. Fiecare apariție a identificatorului MAX în fișierul sursă este înlocuită cu expresia ((x)>(y))?(x):(y), în care parametrii formali x și y sunt înlocuiți cu cei reali. De exemplu, apel macro

va fi înlocuită cu expresia

((1)>(2))?(1):(2)

și un apel macro

va fi înlocuită cu expresia

((i)>(s(i]))?(i):(s(i])

Rețineți că în această macrodefiniție, argumentele cu efecte secundare pot duce la rezultate incorecte. De exemplu, apel macro

va fi înlocuită cu expresia

((i)>(s))?(i):(s)

Operanzii operației > pot fi evaluați în orice ordine, iar valoarea variabilei i depinde de ordinea evaluării. Prin urmare, rezultatul expresiei este imprevizibil. De asemenea, este posibil să fiu incrementat de două ori, ceea ce probabil nu este necesar.

Cel de-al cincilea exemplu definește definiția macro MULT. Apelul macro MULT(3,5) din textul programului este înlocuit cu (3)*(5). Parantezele care includ argumentele reale sunt necesare atunci când argumentele macro sunt expresii complexe. De exemplu, apel macro

ar fi înlocuit cu (3+4)*(5+6), care este egal cu 76. Fără paranteze, rezultatul înlocuirii lui 3+4*5+6 ar fi 29.

Lipirea lexemelor și transformarea argumentelor de definiție macro

SP TS și versiunea 5.0 SP MSC implementează două operații speciale de preprocesor: ## și #.

Într-o directivă #define, două jetoane pot fi „lipite” împreună. Pentru a face acest lucru, acestea trebuie separate prin semne ## (sunt permise caracterele de spațiu la stânga și la dreapta lui ##). Preprocesorul combină astfel de jetoane într-unul singur; de exemplu definiția macro

#definiți VAR (i, j) i##j

cu un apel macro, VAR(x,6) formează identificatorul x6. Unele compilatoare vă permit să utilizați notația x/**/6 în scopuri similare, dar această metodă este mai puțin portabilă.

Caracterul # plasat înaintea unui argument macro indică faptul că acesta trebuie convertit într-un șir de caractere. Când efectuați un apel macro, construcția # este înlocuită cu „”.

Exemplu: definiția macro-ului TRACE permite tipărirea valorilor utilizând funcția standard printf variabile de tip format int = .

#define TRACE(flag) printf (#flag " = %d\n", steag)

Următorul fragment din textul programului:

TRACE (highval);

după procesarea de către preprocesor va lua forma:

printf("highval" " = %d\n", highval);

Următorul prietenșirurile de caractere sunt considerate de compilatorul limbajului C în SP MSC versiunea 5 și în SP TS ca o singură linie, astfel încât înregistrarea rezultată este echivalentă cu următoarea:

printf("highval = %d\n", highval);

Când se efectuează un apel macro, toate argumentele pentru apelul macro sunt mai întâi înlocuite cu macro, apoi sunt substituite în corpul definiției macro. Prin urmare, următorul program va tipări șirul „abatere de la standard”:

#define AB „standard”

#define O „abatere”

#define În „de la standard”

#define CONCAT(P,Q) Р##Q

printf(CONCAT(A,B) "\n");

directiva #undef

Sintaxă:

Directiva #undef anulează acțiunea definiția actuală#definiți pentru. Pentru a nedefini o macrocomandă folosind directiva #undef, pur și simplu specificați-o. Specificarea unei liste de parametri nu este necesară.

Nu este o eroare să aplici #undef unui identificator care nu a fost definit anterior (sau a fost deja nedefinit). Acest lucru poate fi folosit pentru a vă asigura că identificatorul nu este definit.

Directiva #undef este de obicei folosită împreună cu directiva #define pentru a crea o zonă a programului sursă în care este definit un identificator.

#define WIDTH 80

#define ADD(X, Y) (X)+(Y)

În acest exemplu, directiva #undef nu definește constanta numită WIDTH și definiția macro ADD. Rețineți că pentru a anula o definiție macro, specificați doar identificatorul acesteia.

Inclusiv fișierele

Sintaxă:

#include „nume cale”

Directiva #include include conținutul fișierului sursă specificat în fișierul sursă curent compilat. De exemplu, definițiile constante denumite și definițiile macro care sunt comune pentru mai multe fișiere sursă pot fi colectate într-un singur fișier include și incluse cu directiva #include în toate fișierele sursă. Fișierele Include sunt, de asemenea, folosite pentru a stoca declarații de variabile externe și tipuri abstracte date partajate de mai multe fișiere sursă.

Preprocesorul procesează fișierul include în același mod ca și cum fișierul ar fi în întregime parte din fișierul sursă în punctul în care este scrisă directiva #include. Textul inclus poate conține și directive de preprocesor. Preprocesorul procesează fișierul include și apoi revine la procesarea fișierului sursă original.

Calea este un nume de fișier, care poate fi precedat de un nume de dispozitiv și de o specificație de director. Sintaxa numelui căii este determinată de convențiile sistemului de operare.

Preprocesorul folosește conceptul de directoare standard pentru a căuta fișiere incluse. Directoarele standard sunt specificate de comanda PATH a sistemului de operare.

Preprocesorul caută până când găsește un fișier cu numele dat.

Dacă numele căii este specificat fără ambiguitate (în întregime) și cuprins între ghilimele duble, atunci preprocesorul caută fișierul numai în directorul specificat de numele căii specificat și ignoră directoarele standard.

Dacă specificația dată între ghilimele nu se formează Numele complet cale, apoi preprocesorul începe să caute fișierul include în directorul de lucru curent (adică, în directorul care conține fișierul sursă în care este scrisă directiva #include).

Directiva #include poate fi imbricată. Aceasta înseamnă că poate apărea într-un fișier inclus de o altă directivă #include. Când preprocesorul întâlnește o directivă #include imbricată, începe să caute un fișier în directorul curent corespunzător fișierului sursă care conține directiva #include imbricată. Preprocesorul continuă apoi să caute în directorul curent corespunzător fișierului sursă inclus, adică. cel în raport cu care este imbricată această directivă #include. Nivelul permis de imbricare a directivelor #include depinde de implementarea compilatorului. Procesul de căutare în directoarele anexate continuă până când este căutat în directorul curent al primului fișier sursă, adică fișierul al cărui nume a fost specificat la apelarea compilatorului limbajului C.

Preprocesorul continuă apoi căutarea în directoarele specificate pe linia de comandă de compilare și, în final, caută în directoarele standard.

Dacă numele căii este cuprins între paranteze unghiulare, atunci preprocesorul nu va căuta deloc în directorul de lucru curent, ci va începe imediat căutarea în directoarele specificate pe linia de comandă de compilare și apoi în directoarele standard.

#include /* exemplu 1 */

#include "defs.h" /* exemplu 2 */

Primul exemplu include un fișier numit stdio.h în fișierul sursă. Parantezele unghiulare îi spun preprocesorului să caute fișierul în directorul specificat în linia de comandă de compilare și apoi în directoarele standard.

Al doilea exemplu include un fișier numit defs.h în fișierul sursă. Ghilimelele duble înseamnă că atunci când căutați un fișier, directorul care conține fișierul sursă curent trebuie căutat mai întâi.

În SP TS este posibil să setați numele căii în directiva #include folosind o constantă numită. Dacă include este urmată de un identificator, preprocesorul verifică dacă numește o constantă sau o definiție macro. Dacă cuvântul include este urmat de un șir cuprins între ghilimele sau paranteze unghiulare, SP TS nu va căuta numele constantei în el.

#define myinclude "c:\tc\include\mystuff.h"

#include myinclude

#include „myinclude.h”

Prima directivă #include va forța preprocesorul să caute în directorul C:\TC\INCLUDE\MYSTUFF.H, iar a doua va forța preprocesorul să caute fișierul MYINCLUDE.H în directorul curent.

Concatenarea șirurilor de caractere și concatenarea jetoanelor într-o constantă numită, care este utilizată în directiva #include, nu poate fi utilizată. Rezultatul extinderii unei constante trebuie citit imediat ca o directivă #include validă.

Compilare condiționată

Această secțiune descrie directivele care controlează compilarea condiționată. Aceste directive vă permit să excludeți orice părți ale fișierului sursă din procesul de compilare prin verificarea condițiilor (expresii constante).

Directive #if, #elif, #else, #endif

Sintaxă:

Directiva #if, împreună cu directivele #elif, #else și #endif, controlează compilarea părților fișierului sursă. Fiecare directivă #if din același fișier sursă trebuie să aibă o directivă #endif finală. Un număr arbitrar de directive #elif (inclusiv niciuna) și nu mai mult de o directivă #else sunt permise între directivele #if și #endif. Dacă directiva #else este prezentă, atunci între aceasta și directiva #endif există acest nivel Nu ar trebui să existe imbricarea altor directive #elif.

Preprocesorul selectează una dintre zonele de prelucrare. poate cuprinde mai mult de o linie. De obicei, acesta este un site textul programului, cu toate acestea, acest lucru nu este necesar: ​​preprocesorul poate fi folosit pentru a procesa text arbitrar. Dacă conține directive de preprocesor (inclusiv directive de compilare condiționată), atunci aceste directive sunt executate. Textul procesat de preprocesor este trimis spre compilare.

O secțiune de text care nu este selectată de preprocesor este ignorată în etapa de preprocesare și nu este compilată.

Preprocesorul selectează o secțiune de text de procesat pe baza calculului care urmează fiecărei directive #if sau #elif. Este selectată cea care urmează valorii true (nu nulă), până la cea mai apropiată directivă #elif, #else sau #endif asociată cu directiva #if dată.

Dacă nicio expresie constantă restricționată nu este adevărată, atunci preprocesorul o selectează pe cea care urmează directivei #else. Dacă directiva #else lipsește, atunci nu este selectat niciun text.

Expresia constantă restricționată este descrisă în secțiunea 4.2.9 „Expresii constante”. O astfel de expresie nu poate conține dimensiunea operației (în SP TS poate), o operație de turnare de tip, constante de enumerare și constante flotante, dar poate conține operația de preprocesor definită(). Această operație dă adevărat (nu egal cu zero) valoarea, dacă este specificată în prezent; în caz contrar, expresia este falsă (egal cu zero). Amintiți-vă că un identificator definit fără valoare este considerat în continuare definit. Operatorul definit poate fi folosit în mod repetat într-o expresie complexă într-o directivă #if:

#dacă este definit(mysym) || definit(sym)

SP TC (spre deosebire de SP MSC) permite utilizarea mărimii operației într-o expresie constantă limitată pentru preprocesor. Următorul exemplu definește una dintre constante, fie SDATA, fie LDATA, în funcție de dimensiunea pointerului:

#dacă (dimensiunea(void *) == 2)

Directivele #if pot fi imbricate. În acest caz, fiecare dintre directivele #else, #elif, #endif este asociată cu cea mai apropiată directivă #if precedentă.

/* exemplu 1 */

#dacă este definit(CREDIT)

#elif definit (DEBIT)

/* exemplu 2 */

#define SEMNAL 1

#dacă STACKUSE == 1

#derine STACK 200

#define STACK 100

#define SEMNAL 0

#dacă STACKUSE == 1

#define STACK 100

#define STACK 50

/* exemplu 3 */

#elif DLEVEL == 1

#define STACK 100

#elif DLEVEL > 5

display(debugptr);

#define STACK 200

/* exemplu 4 */

#define REG 1 registru

#definiți registrul REG2

#dacă este definit (M_86)

#dacă este definit(M_68000)

#definiți registrul REG4

În primul exemplu, directivele #if, #elif, #else, #endif controlează compilarea unuia dintre cele trei apeluri de funcție. Apelul funcției de credit se compilează dacă este definită constanta numită CREDIT. Dacă este definită o constantă numită DEBIT, apelul funcției de debit este compilat. Dacă niciuna dintre constantele numite nu este definită, atunci apelul funcției printerror este compilat. Vă rugăm să rețineți că CREDIT și credit sunt identificatori diferiți în limbajul C.

Următoarele două exemple presupun că constanta DLEVEL este predefinită de directiva #define.

Al doilea exemplu arată două seturi imbricate de directive #if, #else, #endif. Primul set de directive este procesat dacă valoarea DLEVEL este mai mare de 5. În caz contrar, al doilea set este procesat.

În al treilea exemplu, directivele de compilare catch utilizează valoarea constantei DLEVEL pentru a selecta text. Constanta STACK este definită cu o valoare de 0, 100 sau 200, în funcție de valoarea DLEVEL. Dacă DLEVEL este mai mare de 5, apelul funcției de afișare este compilat și constanta STACK nu este definită.

Cel de-al patrulea exemplu folosește directive de preprocesor pentru a controla aplicarea unei specificații a clasei de memorie de registru într-un program proiectat să ruleze în diferite medii de operare.

Compilatorul alocă de obicei memorie de registru variabilelor în ordinea în care declarațiile variabilelor sunt scrise în program. Dacă un program conține mai multe declarații de variabile ale clasei de memorie de registru decât există registre într-un mediu de operare dat, atunci numai acele variabile ale căror declarații au fost scrise primele vor primi memorie de registru. Prin urmare, dacă variabilele ulterioare sunt utilizate mai intens, câștigurile de eficiență din utilizarea registrelor vor fi neglijabile.

Exemplul arată cum să acordați prioritate memoriei registrului celor mai importante variabile. Constantele numite REG1 și REG2 sunt definite ca cuvinte cheie de registru. Acestea sunt menite să-i declare pe cei mai importanți localnici variabilele funcţiei. De exemplu, în următorul fragment de program aceste variabile sunt b și c.

func(REG3 int a)

Dacă este definită constanta M_86, preprocesorul elimină identificatorii REG3 și REG4 din fișier prin înlocuirea acestora cu text gol. În acest caz, numai variabilele b și c vor primi memorie de registru. Dacă identificatorul M_68000 este definit, atunci toate cele patru variabile sunt declarate cu clasa de memorie de registru.

Dacă niciuna dintre constante nu este definită - nici M_86, nici M_68000 - atunci memoria registrului va fi recepționată de variabilele a, b și c.

Directivele #ifdef și #ifndef

Sintaxă:

Similar directivei #if, directivele #ifdef și #ifndef pot fi urmate de un set de directive #elif și o directivă #else. Setul trebuie terminat cu directiva #endif.

Folosirea directivelor #ifdef și #ifndef este echivalentă cu utilizarea directivei #if folosind o expresie cu o operație definită(). Aceste directive sunt acceptate numai pentru compatibilitate cu Versiuni anterioare Compilatoare în limbaj C. Pentru programe noi, se recomandă utilizarea directivei #if cu operația defined().

Când preprocesorul procesează o directivă #ifdef, verifică dacă directiva #define este definită în prezent. Dacă da, condiția este considerată adevărată, dacă nu, este considerată falsă.

Directiva #line este folosită în mod obișnuit de generatorii automati de programe pentru a se asigura că mesajele de diagnosticare se referă la programul generat mai degrabă decât la fișierul sursă.

în directiva #line poate fi o constantă întreagă arbitrară. poate fi o combinație arbitrară de caractere cuprinse între ghilimele duble. Dacă numele fișierului este omis, numele fișierului original rămâne același.

Numărul de linie curent și numele fișierului sursă sunt disponibile în program prin pseudo-variabile numite __LINE__ și __FILE__. Aceste pseudo-variabile pot fi utilizate pentru a raporta locația exactă a erorii în timpul execuției.

Valoarea pseudovariabilei __FILE__ este un șir care reprezintă numele fișierului, cuprins între ghilimele duble. Prin urmare, imprimarea numelui fișierului sursă nu necesită includerea în sine a identificatorului __FILE__ între ghilimele duble.

/* exemplu 1 */

#linia 151 „copy.s”

/* exemplu 2 */

#define ASSERT(cond) if (!cond)\

(printf("eroare pe linia %d a fișierului %s\n", \

LINE__, __FILE__);) else;

Primul exemplu setează numele fișierului sursă la copy.c și numărul liniei curente la 151.

Al doilea exemplu utilizează pseudovariabilele __LINE__ și __FILE__ din macro-ul ASSERT pentru a imprima un mesaj de eroare care conține coordonatele fișierului sursă dacă o condiție specificată de argumentul macro cond este falsă.

Directiva de tratare a erorilor

Directiva #error este implementată în SP TS. Formatul său:

De obicei, această directivă este scrisă printre directivele de compilare condiționată pentru a detecta o situație ilegală. Folosind directiva #error, preprocesorul anulează compilarea și afișează următorul mesaj:

Fatal: Directiva de eroare:

Fatal - semn eroare fatala; - numele fișierului sursă; - numărul de linie curent; Directiva de eroare - mesaj de eroare din directivă; - textul propriu-zis al mesajului de diagnosticare.

Note pentru compilatorul C

Sintaxă:

Instrucțiunile compilatorului, sau pragma, sunt destinate să fie executate de compilator în timp ce rulează. specifică o instrucțiune specifică compilatorului și eventual argumente.

Setul de pragma pentru fiecare compilator C este diferit. Pentru obtinerea informatii detaliate Pentru pragma, consultați documentația de sistem pentru compilatorul pe care îl utilizați.

Pseudovariabile

Pseudovariabilele sunt constante cu nume rezervate care pot fi utilizate în orice fișier sursă. Fiecare începe și se termină cu două caractere de subliniere (__).

Numărul liniei curente procesate a fișierului sursă este o constantă zecimală. Prima linie a fișierului sursă este numerotată 1.

Numele fișierului sursă compilat este un șir de caractere. Valoarea acestei pseudo variabile se modifică de fiecare dată când compilatorul procesează o directivă #include sau o directivă #line și când fișierul inclus se finalizează.

Următoarele două pseudo-variabile sunt acceptate numai de SP TS.

Data de începere a compilației fișierului sursă curent este un șir de caractere. Fiecare apariție a lui __DATE__ într-un fișier dat produce aceeași valoare, indiferent de cât de mult a avut loc deja procesarea. Data are formatul mmm dd UUUU, unde mmm este luna (ian, februarie, martie, apr, mai, iunie, iul, august, sept, oct, nov, dec), dd este ziua lunii curente (1 ...31 în 1- Poziția a dd are un spațiu dacă numărul este mai mic de 10), aaaa - an (de exemplu, 1990).

Ora de începere a compilației fișierului sursă curent este un șir de caractere. Fiecare apariție a __TIME__ într-un fișier dat produce aceeași valoare, indiferent de cât de mult a avut loc deja procesarea. Ora are formatul hh:mm:ss, unde hh este oră (00…23), mm este minute (00…59), ss este secunde (00…59).

În acest articol vom continua să înțelegem arta programării în C++. În acest moment al tutorialului, este timpul să vă familiarizați cu lucruri precum directivele preprocesorului. Privind în viitor, voi spune că în lecțiile anterioare am folosit deja directiva #include, care este folosit pentru a include fișiere antet.

Mai întâi, să definim ce este un preprocesor. Compilarea oricărui program are loc în mai multe etape, iar una dintre primele este procesarea de către un preprocesor. Dacă vorbim în cuvinte simple, atunci preprocesorul este un program care citește sursă programul și îl modifică pe baza directivelor. Schematic, întregul proces de construire a unui program poate fi reprezentat după cum urmează.

După cum puteți vedea înainte de compilare text original Programul este procesat de un preprocesor, să aruncăm o privire mai atentă la instrucțiunile acestuia.

Să începem cu directiva #include, care este înlocuită de preprocesor cu conținutul fișierului care îl urmează. Exemplu de utilizare a #include:

#include

#include „header2.h”

Dacă numele fișierului este cuprins între paranteze unghiulare, atunci preprocesorul caută fișierul într-o locație predefinită. Utilizarea parantezelor duble implică includerea unui fișier din același director în care se află codul sursă al programului compilat. De asemenea, este de remarcat faptul că fișierele incluse pot conține și directive de preprocesor, în special directiva #include, astfel încât pot apărea probleme cu mai multe incluziuni ale aceluiași fișier. Pentru a evita acest tip de confuzie, au fost introduse directive condiționale, să ne uităm la un exemplu de utilizare a acestora:

#ifndef CUCUMBLER_H

#define CUCUMBLER_H

/* conținutul fișierului cucumbler.h */

Directiva #ifndef verifică dacă constanta CUCUMBLER_H a fost definită anterior, iar dacă răspunsul este negativ, atunci se realizează definiția acestei constante și a altor coduri care urmează înainte de directiva #endif. După cum ați putea ghici, directiva #define definește constanta CUCUMBLER_H. În acest caz, o astfel de bucată de cod ajută la evitarea includerii repetate a aceluiași cod, deoarece după prima includere constanta CUCUMBLER_H este inițializată și verificările ulterioare #ifndef CUCUMBLER_H vor returna FALSE.

Directiva #define este, de asemenea, utilizată pe scară largă la depanarea unui program.

#include

#include

#include

folosind namespace std;

cout<< "Начало функции main()\n";

vector text_array;

în timp ce (cin >> text)

cout<< "Прочитан текст: " << text << "\n";

text_array.push_back(text);

Dacă constanta IN_DEBUG nu este specificată, preprocesorul va genera următoarea sursă:

#include

#include

#include

folosind namespace std;

vector text_array;

în timp ce (cin >> text)

text_array.push_back(text);

Dar dacă definiți IN_DEBUG, textul programului se va schimba radical

#include

#include

#include

folosind namespace std;

cout<< "Начало функции main()\n";

vector text_array;

în timp ce (cin >> text)

cout<< "Прочитан текст: " << text << "\n";

text_array.push_back(text);

Puteți seta constanta preprocesorului direct din consolă. De exemplu, compilatorul g++ folosește următorul format