C forprocessor

C/C++ preprocessor ( eng.  preprocessor , preprocessor) - et program , der forbereder programkoden i C / C++ til kompilering .

Grundlæggende funktioner i præprocessoren

Forprocessoren gør følgende:

Betinget kompilering giver dig mulighed for at vælge, hvilken kode du vil kompilere baseret på:

Preprocessor-trin:

C/C++ præprocessorsproget er ikke Turing komplet, om ikke andet fordi det er umuligt at få præprocessoren til at hænge ved hjælp af direktiver. Se rekursiv funktion (beregnelighedsteori) .

Syntaks af direktiver

Et præprocessordirektiv (kommandolinje) er en linje i kildekoden, der har følgende format: #ключевое_слово параметры:

Søgeordsliste:

Beskrivelse af direktiver

Indsættelse af filer (#include)

Når direktiver #include "..."og findes #include <...>, hvor "..." er et filnavn, læser præprocessoren indholdet af den specificerede fil, udfører direktiver og substitutioner (substitutioner), erstatter direktivet #includemed et direktiv #lineog det behandlede filindhold.

For at #include "..."søge efter en fil udføres det i den aktuelle mappe og mapper, der er angivet på kompilatorens kommandolinje. For at #include <...>søge efter en fil udføres den i mapper, der indeholder standardbiblioteksfiler (stierne til disse mapper afhænger af implementeringen af ​​compileren).

Hvis der findes et direktiv , #include последовательность-лексем der ikke matcher nogen af ​​de foregående former, betragter det sekvensen af ​​tokens som tekst, der som følge af alle makrosubstitutioner skal give #include <...>eller #include "...". Direktivet genereret på denne måde vil blive yderligere fortolket i overensstemmelse med den modtagne formular.

Inkluderede filer indeholder normalt:

Direktivet #includeer normalt angivet i begyndelsen af ​​filen (i headeren), så inkluderede filer kaldes header -filer .

Et eksempel på at inkludere filer fra C -standardbiblioteket .

#include <math.h> // inkludere matematiske funktionserklæringer #include <stdio.h> // include I/O-funktionserklæringer

Brug af en præprocessor anses for ineffektiv af følgende årsager:

  • hver gang filer inkluderes, udføres direktiver og substitutioner (substitutioner); kompilatoren kunne gemme resultaterne af forbehandling til fremtidig brug;
  • flere inkluderinger af den samme fil skal forhindres manuelt ved hjælp af betingede kompileringsdirektiver; compileren kunne udføre denne opgave selv.

Fra 1970'erne begyndte der at dukke metoder op, der erstattede medtagelsen af ​​filer. Java- og Common Lisp-sprogene bruger pakker (søgeord package) (se pakke i Java ),  Pascal bruger engelsk.  enheder (søgeord unitog uses), i Modula , OCaml , Haskell og Python  , moduler. Designet til at erstatte C- og C++- sprogene, bruger D nøgleordene og . moduleimport

Konstanter og makroer #define

Preprocessor konstanter og makroer bruges til at definere små stykker kode.

// konstant #define BUFFER_SIZE ( 1024 ) // makro #define NUMBER_OF_ARRAY_ITEMS( array ) ( sizeof( array ) / sizeof( *(array) ) )

Hver konstant og hver makro erstattes af dens tilsvarende definition. Makroer har funktionslignende parametre og bruges til at reducere overhead af funktionskald i tilfælde, hvor den lille mængde kode, som funktionen kalder, er nok til at forårsage et mærkbart præstationshit.

Eksempel. Definition af makroen max , som tager to argumenter: a og b .

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

En makro kaldes ligesom enhver funktion.

z = max ( x , y );

Efter udskiftning af makroen vil koden se sådan ud:

z = ( ( x ) > ( y ) a ( x ) : ( y ) );

Men sammen med fordelene ved at bruge makroer i C-sproget, for eksempel til at definere generiske datatyper eller fejlfindingsværktøjer, reducerer de også effektiviteten af ​​deres brug noget og kan endda føre til fejl.

For eksempel, hvis f og g  er to funktioner, kaldes

z = max ( f (), g () );

vil ikke evaluere f() én gang og g() én gang , og sætter den største værdi i z , som man kunne forvente. I stedet vil en af ​​funktionerne blive evalueret to gange. Hvis en funktion har bivirkninger, er det sandsynligt, at dens adfærd vil være anderledes end forventet.

C-makroer kan ligne funktioner, skabe ny syntaks til en vis grad, og kan også udvides med vilkårlig tekst (selvom C-kompileren kræver, at teksten er i fejlfri C-kode eller formateret som en kommentar), men de har nogle begrænsninger som softwarestrukturer. Funktionslignende makroer kan for eksempel kaldes som "rigtige" funktioner, men en makro kan ikke overføres til en anden funktion ved hjælp af en markør, fordi selve makroen ikke har nogen adresse.

Nogle moderne sprog bruger typisk ikke denne form for metaprogrammering ved hjælp af makroer som tegnstrengsfuldførelser, der er afhængige af enten automatisk eller manuel ledning af funktioner og metoder, men i stedet andre måder at abstrakte på såsom skabeloner , generiske funktioner eller parametrisk polymorfi . Især inline-funktioner en af ​​de største mangler ved makroer i moderne versioner af C og C++, da en inline-funktion giver makroer fordelen ved at reducere overheaden af ​​et funktionskald, men dens adresse kan sendes i en pointer for indirekte kalder eller bruges som parameter. Ligeledes er problemet med flere evalueringer nævnt ovenfor i maks makroen irrelevant for indbyggede funktioner.

Du kan erstatte #define konstanter med enums og makroer med funktioner inline.

Operatører # og ##

Disse operatorer bruges ved oprettelse af makroer. Operatoren # før en makroparameter omslutter den i dobbelte anførselstegn, for eksempel:

#define make_str( bar ) # bar printf ( make_str ( 42 ) );

forprocessor konverterer til:

printf ( "42" );

Operatoren ## i makroer sammenkæder to tokens, for eksempel:

#define MakePosition( x ) x##X, x##Y, x##Width, x##Height int MakePosition ( Object );

forprocessor konverterer til:

int ObjectX , ObjectY , ObjectWidth , ObjectHeight ; Formel beskrivelse af makrosubstitutioner

1) Kontrollinjen i følgende formular tvinger præprocessoren til at erstatte identifikatoren med en sekvens af tokens gennem resten af ​​programteksten:

#define identifier token_sequence

I dette tilfælde kasseres hvide tegn i begyndelsen og slutningen af ​​sekvensen af ​​tokens. En gentagen #define-linje med samme identifikator betragtes som en fejl, hvis sekvenserne af tokens ikke er identiske (uoverensstemmelser i mellemrumstegn betyder ikke noget).

2) En streng med følgende form, hvor der ikke må være mellemrum mellem den første identifikator og den indledende parentes, er en makrodefinition med parametre specificeret af identifier-list.

#define identifier(list_of_identifiers) sequence_of_tokens

Som i den første form kasseres mellemrumstegnene i begyndelsen og slutningen af ​​tokensekvensen, og makroen kan kun omdefineres med den samme nummer- og navneparameterliste og den samme tokensekvens.

En kontrollinje som denne fortæller præprocessoren at "glemme" definitionen givet til identifikatoren:

#undef identifikator

Anvendelse af #undef-direktivet på en tidligere udefineret identifikator betragtes ikke som en fejl.

{

  • Hvis makrodefinitionen blev specificeret i den anden form, så enhver yderligere streng af tegn i programteksten, bestående af en makro-id (eventuelt efterfulgt af mellemrumstegn), en åbningsparentes, en kommasepareret liste over tokens og en afsluttende parentes, udgør en makroinvokation.
  • Makrokaldsargumenter er kommaseparerede tokens, og kommaer omgivet af anførselstegn eller indlejrede parenteser deltager ikke i argumentadskillelse.
  • (!) Ved gruppering af argumenter udføres makroudvidelse ikke i dem.
  • Antallet af argumenter i makrokaldet skal svare til antallet af makrodefinitionsparametre.
  • Efter at have udtrukket argumenterne fra teksten, kasseres mellemrumstegn, der omgiver dem.
  • Derefter, i erstatningssekvensen af ​​makrotokens, erstattes hver identifikatorparameter uden anførselstegn af det tilsvarende faktiske argument fra teksten.
  • (!)Hvis parameteren ikke er foranstillet af #-tegnet i erstatningssekvensen, og hverken før eller efter det er ##-tegnet, så kontrolleres argumenttokenserne for tilstedeværelsen af ​​makrokald i dem; hvis der er nogen, så udføres udvidelsen af ​​de tilsvarende makroer i den, før argumentet erstattes.

Substitutionsprocessen påvirkes af to specielle operatørskilte.

  • Først, hvis en parameter i en erstatningsstreng af tokens er indledt af et #-tegn, placeres strenganførselstegn (") omkring det tilsvarende argument, og derefter erstattes parameteridentifikatoren sammen med #-tegnet af den resulterende streng-literal. .
    • En omvendt skråstreg indsættes automatisk før hvert "- eller \-tegn, der forekommer omkring eller inde i en streng eller tegnkonstant.
  • For det andet, hvis en sekvens af tokens i en makrodefinition af en hvilken som helst art indeholder ##-tegnet, så kasseres det umiddelbart efter parametersubstitution, sammen med de hvide mellemrumstegn, der omgiver det, på grund af hvilke tilstødende tokens sammenkædes, hvorved der dannes et nyt token.
    • Resultatet er udefineret, når ugyldige sprogtokens genereres på denne måde, eller når den resulterende tekst afhænger af den rækkefølge, som ##-operationen anvendes i.
    • Derudover kan ##-tegnet ikke vises hverken i begyndelsen eller i slutningen af ​​en erstatningssekvens af tokens.

}

  • (!) I makroer af begge typer bliver erstatningssekvensen af ​​tokens genscannet på jagt efter nye definere-identifikatorer.
  • (!) Men hvis en identifikator allerede er blevet erstattet i den aktuelle udvidelsesproces, vil genfremkomsten af ​​en sådan identifikator ikke medføre, at den udskiftes; det vil forblive urørt.
  • (!)Selv hvis den udvidede makroopkaldslinje starter med #-tegnet, vil den ikke blive taget som et præprocessor-direktiv.

Et udråbstegn (!) markerer de regler, der er ansvarlige for rekursiv invokation og definitioner.

Eksempel på makroudvidelse #define cat( x, y ) x ## y

Makrokaldet "cat(var, 123)" vil blive erstattet med "var123". At kalde "cat(cat(1, 2), 3)" vil dog ikke give det ønskede resultat. Overvej forprocessorens trin:

0: kat( kat( 1, 2 ), 3 ) 1: kat( 1, 2 ) ## 3 2: kat(1, 2)3

"##"-operationen forhindrede korrekt udvidelse af argumenterne for det andet "cat"-kald. Resultatet er følgende række af tokens:

kat ( 1 , 2 ) 3

hvor ")3" er resultatet af sammenkædning af det sidste token af ​​det første argument med det første token af ​​det andet argument, er ikke et gyldigt token.

Du kan angive det andet makroniveau som følger:

#define xcat( x, y ) cat( x, y )

Kaldet "xcat(xcat(1, 2), 3)" vil blive erstattet med "123". Overvej forprocessorens trin:

0: xcat( xcat( 1, 2 ), 3 ) 1: kat( xcat( 1, 2 ), 3 ) 2: kat( kat( 1, 2 ), 3 ) 3: kat( 1 ## 2, 3) 4: kat ( 12, 3 ) 5:12##3 6:123

Alt gik godt, fordi "##"-operatøren ikke deltog i udvidelsen af ​​"xcat"-makroen.

Mange statiske analysatorer er ikke i stand til at behandle makroer korrekt, så kvaliteten af ​​statisk analyse reduceres .

Foruddefinerede konstanter #define

Konstanter genereret automatisk af præprocessoren:

  • __LINE__erstattes af det aktuelle linjenummer; det aktuelle linjenummer kan tilsidesættes af direktivet #line; bruges til fejlretning ;
  • __FILE__erstattes af filnavnet; filnavnet kan også tilsidesættes med #line;
  • __FUNCTION__erstattes af navnet på den aktuelle funktion;
  • __DATE__erstattes af den aktuelle dato (på det tidspunkt, hvor koden behandles af forbehandleren);
  • __TIME__erstattes af det aktuelle tidspunkt (på det tidspunkt, hvor koden blev behandlet af præprocessoren);
  • __TIMESTAMP__erstattes af den aktuelle dato og klokkeslæt (på det tidspunkt, hvor koden blev behandlet af præprocessoren);
  • __COUNTER__erstattes af et unikt tal startende fra 0; efter hver udskiftning øges antallet med én;
  • __STDC__erstattes af 1, hvis kompileringen er i overensstemmelse med C-sprogstandarden;
  • __STDC_HOSTED__defineret i C99 og ovenfor; erstattes af 1, hvis udførelsen er under OS -kontrol ;
  • __STDC_VERSION__defineret i C99 og ovenfor; for C99 erstattes det med nummeret 199901, og for C11 erstattes det med nummeret 201112;
  • __STDC_IEC_559__defineret i C99 og ovenfor; konstanten eksisterer, hvis compileren understøtter IEC 60559 flydende kommaoperationer;
  • __STDC_IEC_559_COMPLEX__defineret i C99 og ovenfor; konstanten eksisterer, hvis compileren understøtter IEC 60559-operationer med komplekse tal; C99-standarden forpligter til at understøtte operationer med komplekse tal;
  • __STDC_NO_COMPLEX__defineret i C11; erstattes af 1, hvis operationer med komplekse tal ikke understøttes;
  • __STDC_NO_VLA__defineret i C11; erstattet af 1, hvis arrays med variabel længde ikke understøttes; arrays med variabel længde skal understøttes i C99;
  • __VA_ARGS__defineret i C99 og giver dig mulighed for at oprette makroer med et variabelt antal argumenter.

Betinget kompilering

C-forprocessoren giver mulighed for at kompilere med betingelser. Dette giver mulighed for forskellige versioner af den samme kode. Typisk bruges denne tilgang til at tilpasse programmet til compilerplatformen, tilstanden (den debuggede kode kan fremhæves i den resulterende kode) eller evnen til at kontrollere filforbindelsen nøjagtigt én gang.

Generelt skal programmøren bruge en konstruktion som:

# ifndef FOO_H # definer FOO_H ... ( header filkode )... # Afslut Hvis

Denne "makrobeskyttelse" forhindrer en header-fil i at blive dobbeltinkluderet ved at kontrollere, om der findes den makro, som har samme navn som header-filen. Definitionen af ​​FOO_H-makroen opstår, når header-filen først behandles af præprocessoren. Så, hvis denne header-fil er medtaget igen, er FOO_H allerede defineret, hvilket får præprocessoren til at springe hele teksten i denne header-fil over.

Det samme kan gøres ved at inkludere følgende direktiv i header-filen:

# pragma én gang

Preprocessor-betingelser kan specificeres på flere måder, for eksempel:

# ifdef x ... #andet ... # Afslut Hvis

eller

# ifx ... #andet ... # Afslut Hvis

Denne metode bruges ofte i systemhovedfiler til at teste for forskellige muligheder, hvis definition kan variere afhængigt af platformen; f.eks. bruger Glibc - biblioteket funktionskontrol-makroer til at verificere, at operativsystemet og hardwaren understøtter dem (makroerne) korrekt, mens de bevarer den samme programmeringsgrænseflade.

De fleste moderne programmeringssprog udnytter ikke disse funktioner, idet de stoler mere på traditionelle betingede sætninger if...then...else..., hvilket efterlader compileren med opgaven at udtrække ubrugelig kode fra det program, der kompileres.

Digrafer og trigrafer

Se digrafer og trigrafer på C/C++ sprog.

Forprocessoren behandler digraferne “ %:” (“ #”), “ %:%:” (“ ##”) og trigraferne “ ??=” (“ #”), “ ??/” (“ \”).

Forprocessoren anser sekvensen " %:%: " for at være to tokens ved behandling af C-kode og et token ved behandling af C++-kode.

Se også

Noter

Links