C forprosessor

C-forprosessoren ( cpp ) er forprosessoren for programmeringsspråket C. Det er det første programmet som kalles av kompilatoren og behandler direktiver som #include , #define og #if . Disse direktivene er ikke spesifikke for C. De kan faktisk brukes med alle typer filer.

Forprosessoren bruker 4 trinn kalt Translation Phases . Selv om noen implementeringer kan velge å gjøre noen eller alle fasene samtidig, må den oppføre seg som om de ble utført trinnvis.

Faser

  1. Leksisk tokenisering - Forprosessoren erstatter sekvensen av trigrafer med tegnene de representerer.
  2. Linjespleising - Kodelinjer som fortsetter med escape-sekvenser for nye linjer, kobles sammen for å danne logiske linjer.
  3. Tokenisering - Erstatt kommentarer med tomme felter. Deler hvert av elementene som skal forhåndsbehandles med et skilletegn.
  4. Makroutvidelse og direktivstyring - Utfør linjer med forbehandlingsdirektiver , inkludert de som inkluderer andre filer og de for betinget kompilering. Den utvider også makroene. Siden 1999-versjonen av C håndterer den også operatører #Pragma.

Eksempler

Denne delen diskuterer eksempler på bruk av C-forprosessoren mer detaljert. Gode programmeringspraksis er avgjørende når du skriver C-makroer. Spesielt når du jobber i et team.

Inkludert arkiver

Den vanligste måten å bruke forprosessoren på er å inkludere en annen fil:

#include <stdio.h> int main ( ugyldig ) { printf ( "Hei verden! \n " ); returner 0 ; }

Forbehandleren erstatter linjen #include <stdio.h>med systemoverskriftsfilen med det navnet. I denne overskriften er blant annet funksjonen printf() . Mer spesifikt, der direktivet #includeer plassert, vil det bli erstattet av hele innholdet i 'stdio.h'-filen.

Det kan også skrives med doble anførselstegn: #include "stdio.h". Opprinnelig var dette skillet i stand til å skille mellom systemhodefiler (<>) og brukerhodefiler (""). Gjeldende C-kompilatorer og utviklingsmiljøer har fasiliteter for å indikere hvor de ulike headerfilene er plassert. Imidlertid anbefales det fortsatt å bruke samme nomenklatur for klarhet i koden. Plasseringen av overskriftsfiler kan spesifiseres fra kommandolinjen eller fra en makefil for å gjøre koden mer bærbar.

Enhver utvidelse kan brukes for header-filer. Men etter konvensjon brukes filtypen .h for header-filer og .c for filer som ikke er inkludert av noen andre. Andre utvidelser kan også bli funnet. For eksempel er filer med filtypen .def vanligvis filer hvis innhold er designet for å bli inkludert mange ganger.

#includetvinger vanligvis bruken av beskyttere#include eller direktivet #pragma oncefor å forhindre dobbel inkludering, fordi hvis den samme filen er inkludert mer enn 1 gang, (avhengig av innholdet) kan det føre til at de samme funksjonene eller variabeltypene blir prøvd flere ganger, noe som vil generere en feil ved kompilering, dette forhindres på følgende måte:

#ifndef __FILE_H__ #define __FILE_H__ /*... funksjonserklæringer osv. ...*/ #slutt om

Som et resultat, når du prøver å inkludere filen for andre gang, vil "ifndef"-operasjonen returnere falsk fordi __FILE_H__ allerede var definert første gang den ble inkludert, og følgelig hoppes hele blokken over til den når "endif" som er vanligvis på slutten av filen.

Betinget kompilering

Direktivene #ifdef, #ifndef, #else, #elifog #endifkan brukes til å utføre betingede kompilasjoner.

#define __WINDOWS__ #ifdef __WINDOWS__ #inkluder < windows.h> #else #include <unistd.h> #slutt om

Den første linjen definerer en __WINDOWS__ makro . Makroer kan defineres av kompilatoren, kan spesifiseres på kommandolinjen til kompilatoren, eller kan kontrollere programkompilering fra en makefile .

Følgende kode sjekker om __WINDOWS__-makroen er definert. I så fall, som i eksemplet, er filen inkludert <windows.h>, ellers er <unistd.h>.

Eksempler på annen bruk

Fordi forprosessoren kan påkalles uavhengig av kildekodekompileringsprosessen, kan den også brukes som en generell forprosessor for andre typer tekstbehandling. Se forbehandlere for generelle formål for andre eksempler.

Makrodefinisjon og utvidelse

Det finnes to typer makroer: de som er som objekter og de som er som funksjoner . De som ligner funksjoner tar parametere mens de som ligner objekter ikke gjør det. Måten å definere en identifikator som en makro av hver type er henholdsvis:

#define <identifikator> <liste over tokens som skal erstattes> #define <identifier>(<liste over parametere>) <liste over tokens som skal erstattes>

Merk at det ikke er noe mellomrom mellom makroidentifikatoren og venstre parentes.

Hver gang identifikatoren vises i kildekoden, vil den bli erstattet av listen over tokens. Selv om det er tomt. Identifikatorer som er deklarert som funksjoner, erstattes bare når de kalles opp med parameterne makroen ble definert med.

Objektlignende makroer brukes vanligvis som en del av god programmeringspraksis for å lage symbolske navn for konstanter. For eksempel:

#define PI 3.14159

i stedet for å bruke nummeret slik det står i koden.

Et eksempel på en makro som fungerer som en funksjon er:

#define RADAGRA(x) ((x) * 57.29578)

Denne makroen definerer hvordan radianer skal konverteres til grader. Så kan vi skrive RADAGRA(34). Forprosessoren vil erstatte makrokallet med multiplikasjonen definert ovenfor. Makroene som vises her er skrevet med store bokstaver. Dette lar deg enkelt skille mellom makroer og funksjoner kompilert i kildekoden.

Forrang

Merk at makroer bruker parenteser rundt argumentene og rundt hele uttrykket. Hvis noen av disse utelates, kan det oppstå uønskede effekter senere. For eksempel:

  • Uten parentes i argumentene:
  • Makro definert som #define RADAGRA(x) (x * 57.29578)
  • RADAGRA(a + b) utvides som (a + b * 57,29578)
  • Uten parentes i uttrykket:
  • Makro definert som #define RADAGRA(x) (x) * 57.29578
  • 1 / RADAGRA(a) utvides som 1 / (a) * 57.29578

sannsynligvis ikke samsvarer med ønsket effekt.

Flere vurderinger av bivirkninger

Et annet eksempel på makroer av funksjonstype er:

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

Dette illustrerer en av farene ved å bruke makroer som funksjoner. Ett av argumentene ( a eller b ) vil bli evaluert to ganger når "funksjonen" kalles. Så hvis uttrykket evalueres, vil MIN(++primernumero, segundonumero)argumentet firstnumber økes to ganger og ikke ett som forventet.

En sikrere måte å oppnå det samme målet på er å bruke typen konstruksjon .

#define max(a,b) \ ({ typeof (a) _a = (a); \ typeof (b) _b = (b); \ _a > _b ? _a : _b; })

Dette gjør det slik at argumentene kun vurderes én gang. Og dessuten at det ikke er spesifikt for datatypen. Denne konstruksjonen er ikke lovlig i ANSI C. Både nøkkelordet typeofog å plassere sammensatte setninger innenfor parentes er ikke-standard utvidelser implementert i GNU C-kompilatoren (gcc). Ved å bruke gcc-kompilatoren kan du løse det samme problemet ved å bruke statiske innebygde funksjoner . Disse er like effektive som deres ekvivalenter #define. Inline - funksjoner lar kompilatoren sjekke typen parametere. I dette spesielle tilfellet kan det være en ulempe fordi 'maks'-funksjonen fungerer med forskjellige typer parametere. Men generelt sett er det vanligvis en fordel.

Med ANSI C er det ingen generell løsning for å håndtere bieffekten av argumenter i makroer.

Strengesammenkobling

Strengesammenkobling er en av de mer subtile og lett misbrukte funksjonene. To argumenter kan slås sammen ved hjelp av operatoren ##. Denne funksjonen lar deg sette sammen to strenger i forprosessorkoden. Dette kan brukes til å bygge svært forseggjorte makroer som fungerer nesten som C ++- maler . Uten mange av dens fordeler.

For eksempel:

#define MYCASE(_item,_id) \ case _id: \ _item##_##_id=_id;\ break bryter ( x ) { MYCASE ( widget , 23 ); }

Linjen MYCASE(widget,23)utvides som case 23: widget_23=23; break. Semikolonet etter høyre parentes utvides ikke, men konverteres til semikolonet som fullfører setningen break.

Merk at _ mellom ##er 'bokstavelig' mens de i _id- og _item- argumentene er navnene på makroens 'argumenter'.

Semikolon

Som vist i eksemplet ovenfor er semikolon på den siste linjen i makrodefinisjonen utelatt og makroen ser "naturlig" ut når den skrives. Det kan inkluderes i makrodefinisjonen, men da vil vi ende opp med linjer i koden uten semikolon på slutten. Dette kan villede alle som leser koden. Eller enda verre, brukeren kan bli fristet til å inkludere semikolon uansett. I de fleste tilfeller kan det være ufarlig (et ekstra semikolon angir en tom setning), men det kan forårsake feil i flytkontrollblokker:

#define PRINT_FORMATED(s) \ printf("Melding: \"%s\"\n", s); hvis ( n < 10 ) PRINT_FORMATED ( "n er mindre enn 10" ); ellers PRINT_FORMATED ( "n er minst 10" );

Dette utvides til to utsagn. Den som er tiltenkt printfog en tom setning i hver gren av if/else -konstruksjonen . Dette får kompilatoren til å gi en feilmelding som ligner på denne:

error: expected expression before ‘else’ gcc 4.1.1

Flere linjer

Makroer kan utvide så mange linjer som nødvendig. For å gjøre dette, er alt du trenger å gjøre å avslutte hver linje med en strek til venstre (\). Makroen avsluttes på den siste linjen uten innledende bindestrek.

Riktig brukt kan flerlinjemakroer redusere størrelsen og kompleksiteten til kildekoden til et C-program betraktelig. Dette øker kodens lesbarhet og vedlikeholdsvennlighet.

Siterer makroargumenter

Selv om en streng som sendes til en makro ikke er omgitt av anførselstegn, kan den behandles som om den brukte "#"-direktivet. For eksempel i makroen:

#define MELLOM SITAT(x) #x

koden

printf ( "%s \n " , MELLOM SITAT ( 1 + 2 ));

vil utvide som

printf ( "%s \n " , "1+2" );

Denne funksjonen kan brukes med automatisk strengsammenkobling for å feilsøke makroer. Som i følgende eksempel:

#define dumpme(x, fmt) printf("%s:%u: %s=" fmt, __FILE__, __LINE__, #x, x) int some_function () { int foo ; /* [mye komplisert kode går her] */ dumpme ( foo , "%d" ); /* [og mer komplisert kode her] */ }

vil skrive ut navnet på uttrykket og dets verdi samt navnet på filen og linjen der det utføres.

Variable makroer

ANSI C tillater ikke makroer som har et variabelt antall argumenter. Men dette alternativet ble introdusert av forskjellige kompilatorer og ble standardisert i C99 . Variable makroer er spesielt nyttige når du skriver innpakninger for printf. For eksempel for å vise advarsler eller feilmeldinger.

Makroer X

Et lite kjent bruksmønster for C-forprosessoren er kjent som "X Macros". De er praksisen med å bruke direktivet flere ganger #includei samme overskriftsfil. Hver gang i et annet makrodefinisjonsmiljø.

Fil : kommandoer . def KOMMANDO ( LEGG TIL , "Kommando for å legge til" ) KOMMANDO ( SUB , "Kommando for å trekke fra" ) COMMAND ( XOR , "Command Exclusive-OR" ) enum kommandoindekser { #define COMMAND(navn, beskrivelse) COMMAND_##name , #inkluder "commandos.def" #undef COMMAND NUMBER_COMMANDS /* Antall kommandoer definert */ }; char * kommandobeskrivelser [] = { #define COMMANDO(navn, beskrivelse) beskrivelse , #inkluder "commands.def" #undef KOMMANDO NULL }; result_t handler_ADD ( status_t * ) { /* kode for ADD her */ } result_t SUB_manager ( t_state * ) { /* kode for SUB her */ } result_t handler_XOR ( status_t * ) { /* XOR-kode her */ } typedef t_result ( * t_command_handler )( t_state * ); t_command_manager command_managers [ ] = { #define COMMAND(navn, beskrivelse) &manager_##navn, #inkluder "commands.def" #undef KOMMANDO NULL };

Når du vil legge til en ny kommando, må du bare endre header-filen der vi har definert Macro X (normalt med .def- utvidelsen ) og definere en ny behandler for den kommandoen. Beskrivelseslisten, behandlerlisten og oppregningen vil automatisk bli oppdatert av forbehandleren. Imidlertid er denne automatiseringen i mange tilfeller ikke mulig.

Brukerdefinerte byggefeil og advarsler

Direktivet #errorsetter inn en feilmelding i kompilatorutgangen.

#error "ALVÆR FEIL!"

Dette viser "FATAL ERROR!" i kompilatorutgangen og stopper byggingen på dette tidspunktet. Dette er veldig nyttig hvis du ikke vet om en linje er kompilert eller ikke. Det er også nyttig når du har veldig parameterisert kode og du vil vite om en #definebestemt har blitt lagt inn fra makefilen. Som her:

#ifdef WINDOWS ... /* Windows-spesifikk kode */ #elif definert(UNIX) ... /* Unix-spesifikk kode */ #else #feil "Hva er operativsystemet ditt?" #slutt om

Direktivet kan også brukes #warningtil å vise meldinger i kompilatorutgangen uten å stoppe prosessen. En bruk for dette er å vise gammel kode som ikke bør brukes, men er av kompatibilitetsgrunner.

#warning "ABC er avviklet. Bruk XYZ i stedet."

Visual Studio.NET støtter ikke dette direktivet og #pragma message.

Selv om teksten til #feil- og #advarselsdirektivene ikke trenger å være vedlagt anførselstegn, er det god praksis å bruke dem. Hvis de ikke brukes, kan det bli funnet feil med apostrof og andre tegn som forbehandleren kan prøve å tolke.

Kompilatorspesifikke prosessorfunksjoner

Direktiver #pragmaer kompilatorspesifikke direktiver . Hver kompilator kan bruke dem til det de mener er nødvendig. For eksempel for å undertrykke en spesifikk feilmelding.

Makroplasseringsstandard

Noen symboler er forhåndsdefinert i ANSI C. De vanligste er __FILE__og __LINE__som utvides som henholdsvis filnavn og gjeldende linje.

// feilsøker makroer slik at jeg kan se hvor en melding kommer fra. #define STRINGWHERE "[fil %s, linje %d] " #define ARGUMENTSHERE __FILE__,__LINE__ printf ( STRING WHERE ": se, x=%d \n " , WHEREARGUMENTS , x );

Dette viser verdien av xfilen og linjenummeret foran. Merk at argumentet CADENADONDEer koblet sammen med strengen som følger det.

Forhåndsdefinerte kompilatorspesifikke makroer

Du må vanligvis sjekke den kompilatorspesifikke dokumentasjonen. Eller se prosjektet [ 1 ]​ som opprettholder en liste over de spesielle egenskapene til hver kompilator. Du kan også få kompilatoren til å fortelle oss noen nyttige forhåndsdefinerte makroer:

  • For GNU C-kompilatoren:gcc -dM -E - < /dev/null
  • For HP-UX ANSI C-kompilatoren, hvor fred.cer en testfil:cc -v fred.c

Se også

Referanser

  1. "Forhåndsdefinerte kompilatormakroer " . Hentet 29. august 2013 . 

Eksterne lenker