Directivele de programare C / preprocesor și macrocomenzile

sunt instrucțiuni speciale direcționate către preprocesor (Directiva preprocesorului) sau către compilator (Directiva compilatorului) cu privire la modul în care ar trebui să proceseze o parte sau tot codul sursă sau să seteze unele steaguri pe obiectul final și sunt utilizate pentru a face scrierea codului sursă mai ușoară (mai portabilă, de exemplu) și pentru a face codul sursă mai ușor de înțeles. Directivele sunt gestionate de preprocesor, care este fie un program separat invocat de compilator, fie o parte a compilatorului în sine.

#includeEdit

C are unele caracteristici ca parte a limbajului și altele ca parte a unei biblioteci standard, care este un depozit de cod care este disponibil alături de fiecare compilator C conform standard. Când compilatorul C compilează programul, de obicei îl leagă și de biblioteca C standard. De exemplu, la întâlnirea cu o directivă #include <stdio.h>, aceasta înlocuiește Directiva cu conținutul stdio.h fișier antet.

când utilizați caracteristici din bibliotecă, C vă cere să declarați ceea ce ați folosi. Prima linie din program este o directivă de preprocesare care ar trebui să arate astfel:

#include <stdio.h>

linia de mai sus determină declarațiile C care sunt în stdio.h antet pentru a fi incluse pentru a fi utilizate în programul dumneavoastră. De obicei, acest lucru este implementat doar prin introducerea în programul dvs. a conținutului unui fișier antet numit stdio.h, situat într-o locație dependentă de sistem. Locația acestor fișiere poate fi descrisă în documentația compilatorului. O listă de fișiere antet c standard este listată mai jos în tabelul anteturi.

stdio.antetul h conține diverse declarații pentru intrare/ieșire (I/o) folosind o abstractizare a mecanismelor I / O numite fluxuri. De exemplu, există un obiect de flux de ieșire numit stdout, care este utilizat pentru a emite text la ieșirea standard, care afișează de obicei textul pe ecranul computerului.

dacă utilizați paranteze unghiulare ca exemplul de mai sus, preprocesorul este instruit să caute fișierul include de-a lungul căii mediului de dezvoltare pentru standardul include.

#include "other.h"

dacă utilizați ghilimele (” „), preprocesorul este de așteptat să caute în unele locații suplimentare, de obicei definite de utilizator, pentru fișierul antet și să revină la standard include căi numai dacă nu se găsește în acele locații suplimentare. Este obișnuit ca acest formular să includă căutarea în același director ca fișierul care conține directiva #include.

notă: ar trebui să verificați documentația mediului de dezvoltare pe care îl utilizați pentru orice implementări specifice furnizorului Directivei #include.

HeadersEdit

The C90 standard headers list:

  • <assert.h>
  • <ctype.h>
  • <errno.h>
  • <float.h>
  • <limits.h>
  • <locale.h>
  • <math.h>
  • <setjmp.h>
  • <signal.h>
  • <stdarg.h>
  • <stddef.h>
  • <stdio.h>
  • <stdlib.h>
  • <string.h>
  • <time.h>

Headers added since C90:

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <iso646.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>
  • <wchar.h>
  • <wctype.h>

#pragmaEdit

Directiva pragma (informații pragmatice) face parte din standard, dar semnificația oricărei pragma depinde de implementarea software a standardului utilizat. Directiva # pragma oferă o modalitate de a solicita un comportament special de la compilator. Această directivă este cea mai utilă pentru programele neobișnuit de mari sau care trebuie să profite de capacitățile unui anumit compilator. Pragmas sunt utilizate în cadrul programului sursă.

#pragma token(s)
  1. pragma este de obicei urmată de un singur jeton, care reprezintă o comandă pentru compilator să se supună. Ar trebui să verificați implementarea software a standardului C pe care intenționați să îl utilizați pentru o listă a jetoanelor acceptate. Nu este surprinzător că setul de comenzi care pot apărea în directivele #pragma este diferit pentru fiecare compilator; va trebui să consultați documentația pentru compilatorul dvs. pentru a vedea ce comenzi permite și ce fac acele comenzi.

de exemplu, una dintre cele mai implementate directive preprocesor,#pragma once când este plasat la începutul unui fișier antet, indică faptul că fișierul în care se află va fi omis dacă este inclus de mai multe ori de către preprocesor.

notă: există alte metode pentru a face această acțiune, care este denumit în mod obișnuit ca folosind includ paznici.

#defineEdit

avertisment: macro-urile preprocesorului, deși tentante, pot produce rezultate destul de neașteptate dacă nu sunt făcute corect. Rețineți întotdeauna că macrocomenzile sunt substituții textuale făcute codului sursă înainte de a fi compilat ceva. Compilatorul nu știe nimic despre macro-uri și nu ajunge să le vadă. Acest lucru poate produce erori obscure, printre alte efecte negative. Preferați să utilizați caracteristici de limbă, dacă există echivalente (în exemplu utilizați const int sau enum în loc de #defined constante).

acestea fiind spuse, există cazuri, în cazul în care macro-uri sunt foarte utile (a se vedeadebug macro de mai jos pentru un exemplu).

Directiva #define este utilizată pentru a defini valori sau macro-uri care sunt utilizate de preprocesor pentru a manipula codul sursă al programului înainte de a fi compilat. Deoarece definițiile preprocesorului sunt înlocuite înainte ca compilatorul să acționeze asupra codului sursă, orice erori introduse de #define sunt dificil de urmărit.

prin convenție, valorile definite folosind #define sunt denumite cu majuscule. Deși acest lucru nu este o cerință, se consideră o practică foarte proastă de a face altfel. Acest lucru permite identificarea cu ușurință a valorilor la citirea codului sursă.

astăzi, #define este utilizat în principal pentru a gestiona diferențele de compilator și platformă. De exemplu, o definire ar putea deține o constantă care este codul de eroare adecvat pentru un apel de sistem. Utilizarea #define ar trebui astfel limitată, cu excepția cazului în care este absolut necesar; declarațiile typedef și variabilele constante pot îndeplini adesea aceleași funcții mai sigur.

o altă caracteristică a comenzii #define este că poate lua argumente, făcându-l destul de util ca creator de pseudo-funcții. Luați în considerare următorul cod:

#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}

în general, este o idee bună să folosiți paranteze suplimentare atunci când utilizați macrocomenzi complexe. Observați că în exemplul de mai sus, variabila „x” se află întotdeauna în propriul set de paranteze. În acest fel, va fi evaluat în întregime, înainte de a fi comparat cu 0 sau înmulțit cu -1. De asemenea, întreaga macrocomandă este înconjurată de paranteze, pentru a preveni contaminarea acesteia cu alt cod. Dacă nu sunteți atent, riscați ca compilatorul să vă interpreteze greșit codul.

Din cauza efectelor secundare este considerată o idee foarte proastă de a utiliza funcțiile macro așa cum este descris mai sus.

int x = -10;int y = ABSOLUTE_VALUE( x++ );

dacă ABSOLUTE_VALUE() ar fi o funcție reală ‘x’ ar avea acum valoarea ‘-9’, dar pentru că a fost un argument într-o macro a fost extins de două ori și astfel are o valoare de -8.

exemplu:

pentru a ilustra pericolele macrocomenzilor, luați în considerare această macrocomandă naivă

#define MAX(a,b) a>b?a:b

și Codul

i = MAX(2,3)+5;j = MAX(3,2)+5;

aruncați o privire la aceasta și luați în considerare care ar putea fi valoarea după execuție. Declarațiile sunt transformate în

int i = 2>3?2:3+5;int j = 3>2?3:2+5;

astfel, după executarea i=8 și j=3 în loc de rezultatul așteptat al i=j=8! Acesta este motivul pentru care ați fost avertizat să utilizați un set suplimentar de paranteze de mai sus, dar chiar și cu acestea, drumul este plin de pericole. Cititorul de alertă ar putea realiza rapid că dacăa saub conține expresii, definiția trebuie să parantezeze fiecare utilizare a lui A,b în definiția macro, astfel:

#define MAX(a,b) ((a)>(b)?(a):(b))

aceasta funcționează, cu condiția ca a,b să nu aibă efecte secundare. Într-adevăr,

i = 2;j = 3;k = MAX(i++, j++);

ar avea ca rezultat k=4, i=3 și j=5. Acest lucru ar fi extrem de surprinzător pentru oricine se așteaptă ca MAX() să se comporte ca o funcție.

deci, care este soluția corectă? Soluția este să nu folosiți deloc macro. O funcție globală, inline, ca aceasta

inline int max(int a, int b) { return a>b?a:b }

nu are niciuna dintre capcanele de mai sus, dar nu va funcționa cu toate tipurile.

notă: explicitinline declarația nu este cu adevărat necesar, cu excepția cazului în definiția este într-un fișier antet, deoarece compilator poate inline funcții pentru tine (cu gcc acest lucru se poate face cu-finline-functions sau-O3). Compilatorul este adesea mai bun decât programatorul la prezicerea funcțiilor care merită să fie introduse. De asemenea, apelurile funcționale nu sunt cu adevărat scumpe (erau).

compilatorul este de fapt liber să ignoreinline cuvânt cheie. Este doar un indiciu (cu excepția faptului că inline este necesar pentru a permite definirea unei funcții într-un fișier antet fără a genera un mesaj de eroare datorită funcției definite în mai multe unități de traducere).

(#,##)

operatorii # și ## sunt utilizați cu macrocomanda #define. Utilizarea # face ca primul argument după # să fie returnat ca un șir între ghilimele. De exemplu, comanda

#define as_string( s ) # s

va face compilatorul să transforme această comandă

puts( as_string( Hello World! ) ) ;

în

puts( "Hello World!" );

folosind ## concatenează ceea ce este înainte de ## cu ceea ce este după el. De exemplu, comanda

#define concatenate( x, y ) x ## y...int xy = 10;...

va face compilatorul să transforme

printf( "%d", concatenate( x, y ));

în

printf( "%d", xy);

care va afișa, desigur, 10 la ieșirea standard.

este posibilă concatenarea unui argument macro cu un prefix sau sufix constant pentru a obține un identificator valid ca în

#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )

care va defini o funcție numită my_bar(). Dar nu este posibil să integrați un argument macro într-un șir constant folosind operatorul de concatenare. Pentru a obține un astfel de efect, se poate utiliza proprietatea ANSI C că două sau mai multe constante de șir consecutive sunt considerate echivalente cu o singură constantă de șir atunci când sunt întâlnite. Folosind această proprietate, se poate scrie

#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )

pe care macro-procesorul îl va transforma în

puts( "I'm eating " "fruit" " today." )

care la rândul său va fi interpretat de parserul C ca o constantă a unui singur șir.

următorul truc poate fi folosit pentru a transforma o constantă numerică în literali șir

#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));

acesta este un pic dificil, deoarece este extins în 2 pași. Primul num2str(CONST) este înlocuit cu str(23), care la rândul său este înlocuit cu "23". Acest lucru poate fi util în următorul exemplu:

#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif

Acest lucru vă va oferi un mesaj de depanare frumos, inclusiv fișierul și linia în care a fost emis mesajul. Dacă depanare nu este definit cu toate acestea mesajul de depanare va dispărea complet din Codul. Aveți grijă să nu utilizați acest tip de construct cu nimic care are efecte secundare, deoarece acest lucru poate duce la bug-uri, care apar și dispar în funcție de parametrii de compilare.

macrosEdit

macrocomenzile nu sunt verificate de tip și astfel nu evaluează argumentele. De asemenea, ei nu se supun în mod corespunzător domeniului de aplicare, ci pur și simplu iau șirul care le-a fost transmis și înlocuiesc fiecare apariție a argumentului macro din textul macro-ului cu șirul real pentru acel parametru (codul este literalmente copiat în locația din care a fost apelat).

un exemplu de utilizare a unei macrocomenzi:

 #include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }

— rezultatul „a” ar trebui să fie „2” (b + C = 16 -> trecut la Adăugare -> 16 / SLICES -> rezultatul este „2”)

notă:
De obicei este o practică proastă să definiți macrocomenzi în anteturi.

o macrocomandă ar trebui definită numai atunci când nu este posibil să se obțină același rezultat cu o funcție sau cu un alt mecanism. Unele compilatoare sunt capabile să optimizeze codul în cazul în care apelurile către funcții mici sunt înlocuite cu cod inline, negând orice avantaj posibil de viteză.Utilizarea typedefs, enums și inline (în C99) este adesea o opțiune mai bună.

una dintre puținele situații în care funcțiile inline nu vor funcționa-deci sunteți destul de mult forțați să utilizați macro-uri asemănătoare funcțiilor-este inițializarea constantelor de timp de compilare (inițializarea statică a structurilor).Acest lucru se întâmplă atunci când argumentele pentru macro sunt literale pe care compilatorul le poate optimiza la un alt literal.

#errorEdit

Directiva #error oprește compilarea. Atunci când unul este întâlnit standardul specifică faptul că compilatorul ar trebui să emită un diagnostic care conține jetoanele rămase în directivă. Acest lucru este folosit mai ales în scopuri de depanare.

programatorii folosesc „#error” în interiorul unui bloc condițional, pentru a opri imediat compilatorul atunci când „#if” sau „#ifdef”-la începutul blocului-detectează o problemă de compilare.În mod normal, compilatorul omite blocul (și Directiva „#error” din interiorul acestuia) și compilarea continuă.

 #error message

#warningEdit

multe compilatoare acceptă o directivă #warning. Când se întâlnește unul, compilatorul emite un diagnostic care conține jetoanele rămase în directivă.

 #warning message

#undefEdit

Directiva #undef anulează o macrocomandă. Identificatorul nu trebuie să fi fost definit anterior.

#if,#else,#elif,#endif (condiționale)editează

comanda #if verifică dacă o expresie condițională de control evaluează la zero sau zero și exclude sau include un bloc de cod. De exemplu:

 #if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif

expresia condiționată ar putea conține orice operator C, cu excepția operatorilor de atribuire, a operatorilor de incrementare și decrementare, a adresei operatorului și a dimensiunii operatorului.

un operator unic utilizat în preprocesare și nicăieri altundeva nu este operatorul definit. Returnează 1 Dacă numele macro, opțional inclus între paranteze, este definit în prezent; 0 dacă nu.

comanda #endif încheie un bloc pornit de#if#ifdef sau#ifndef.

comanda #elif este similară cu#if, cu excepția faptului că este utilizată pentru a extrage unul dintr-o serie de blocuri de cod. De exemplu:

 #if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */

#ifdef,#ifndefEdit

comanda #ifdef este similară cu#if, cu excepția faptului că blocul de cod care urmează este selectat dacă este definit un nume macro. În acest sens,

#ifdef NAME

este echivalent cu

#if defined NAME

comanda #ifndef este similară cu #ifdef, cu excepția faptului că testul este inversat:

#ifndef NAME

este echivalent cu

#if !defined NAME

#LINEEDIT

această directivă preprocesor este utilizată pentru a seta numele fișierului și numărul liniei liniei care urmează directivei la valori noi. Aceasta este utilizată pentru a seta macrocomenzile __FILE__ și __LINE__.

Lasă un răspuns

Adresa ta de email nu va fi publicată.