C-Programmering/preprocessor direktiver og makroer

Direktiver er spesielle instruksjoner rettet til preprocessor (preprocessor directive) eller til kompilatoren (compiler directive) om hvordan det skal behandle deler av eller hele kildekoden din eller sette noen flagg på det endelige objektet og brukes til å gjøre skriving av kildekode enklere (mer bærbar for eksempel) og for å gjøre kildekoden mer forståelig. Direktiver håndteres av preprosessoren, som enten er et eget program påberopt av kompilatoren eller en del av kompilatoren selv.

#includeEdit

C har noen funksjoner som en del av språket og noen andre som en del av et standardbibliotek, som er et lager av kode som er tilgjengelig sammen med alle Standardkonformante c-kompilatorer. Når c kompilatoren kompilerer programmet det vanligvis også kobler den med standard c bibliotek. For eksempel, ved å møte et #include <stdio.h> – direktiv, erstatter det direktivet med innholdet i stdio.h header fil.

Når Du bruker funksjoner fra biblioteket, krever C at Du erklærer hva Du vil bruke. Den første linjen i programmet er et forbehandlingsdirektiv som skal se slik ut:

#include <stdio.h>

linjen ovenfor forårsaker c-deklarasjonene som er i stdio.h header å bli inkludert for bruk i programmet. Vanligvis er dette implementert ved bare å sette inn i programmet innholdet i en header fil kalt stdio.h, plassert i en systemavhengig plassering. Plasseringen av slike filer kan beskrives i kompilatorens dokumentasjon. En liste over standard c-headerfiler er oppført nedenfor i Overskriftstabellen.

stdio.h header inneholder ulike erklæringer for input/output (i / O) ved hjelp av en abstraksjon av i / O mekanismer kalt strømmer. For eksempel er det et utgangsstrømobjekt kalt stdout som brukes til å sende tekst til standardutgangen, som vanligvis viser teksten på dataskjermen.

hvis du bruker vinkelparenteser som eksemplet ovenfor, instrueres preprosessoren til å søke etter inkluder-filen langs utviklingsmiljøbanen for standarden inkluderer.

#include "other.h"

hvis du bruker anførselstegn ( «» ), forventes preprosessoren å søke i noen ekstra, vanligvis brukerdefinerte steder for topptekstfilen, og å falle tilbake til standarden inkluderer bare baner hvis den ikke finnes i de ekstra stedene. Det er vanlig at dette skjemaet inkluderer søk i samme katalog som filen som inneholder #include-direktivet.

MERK: du bør sjekke dokumentasjonen for utviklingsmiljøet du bruker for eventuelle leverandørspesifikke implementeringer av # include-direktivet.

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

pragma (pragmatisk informasjon) direktivet er en del av standarden, men betydningen av enhver pragma er avhengig av standarden.programvareimplementering av standarden som brukes. # Pragma-direktivet gir en måte å be om spesiell oppførsel fra kompilatoren. Dette direktivet er mest nyttig for programmer som er uvanlig store eller som trenger å dra nytte av evnen til en bestemt kompilator.

Pragmas brukes i kildeprogrammet.

#pragma token(s)
  1. pragma blir vanligvis etterfulgt av et enkelt token, som representerer en kommando for kompilatoren å adlyde. Du bør sjekke programvareimplementeringen Av C-standarden du har tenkt å bruke for en liste over støttede tokens. Ikke overraskende er settet med kommandoer som kan vises i # pragma-direktiver forskjellig for hver kompilator; du må konsultere dokumentasjonen for kompilatoren din for å se hvilke kommandoer det tillater og hva disse kommandoene gjør.

for eksempel en av de mest implementerte preprosessor direktiver, #pragma once når plassert i begynnelsen av en header fil, indikerer at filen der den ligger vil bli hoppet over hvis inkludert flere ganger av preprosessoren.

MERK: Andre metoder finnes for å gjøre denne handlingen som ofte refereres til som å bruke inkluderer vakter.

#defineEdit

ADVARSEL: Preprosessormakroer, selv om de er fristende, kan gi ganske uventede resultater hvis de ikke gjøres riktig. Alltid huske på at makroer er tekstlig erstatninger gjort til kildekoden før noe er kompilert. Kompilatoren vet ikke noe om makroene og får aldri se dem. Dette kan gi obskure feil, blant andre negative effekter. Foretrekker å bruke språkfunksjoner, hvis det er tilsvarende (i eksempel bruk const int eller enum i stedet for #defined konstanter).

Når det er sagt, er det tilfeller der makroer er svært nyttige (se debug makro nedenfor for et eksempel).

#define-direktivet brukes til å definere verdier eller makroer som brukes av preprosessoren til å manipulere programkilden før den kompileres. Fordi preprosessordefinisjoner erstattes før kompilatoren virker på kildekoden, er eventuelle feil som er introdusert av # define vanskelig å spore.

ved konvensjon er verdier definert ved hjelp av #define navngitt i store bokstaver. Selv om det ikke er et krav, anses det som veldig dårlig praksis å gjøre noe annet. Dette gjør at verdiene lett kan identifiseres når du leser kildekoden.

i Dag brukes #define primært til å håndtere kompilator-og plattformforskjeller. For eksempel kan en definere holde en konstant som er riktig feilkode for et systemkall. Bruken av # define bør derfor begrenses med mindre det er absolutt nødvendig; typedef-setninger og konstante variabler kan ofte utføre de samme funksjonene mer trygt.

En annen funksjon i #define-kommandoen er at den kan ta argumenter, noe som gjør den ganske nyttig som en pseudo-funksjonsskaper. Vurder følgende kode:

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

det er generelt en god ide å bruke ekstra parenteser når du bruker komplekse makroer. Legg merke til at i eksemplet ovenfor er variabelen » x » alltid innenfor sitt eget sett med parenteser. På denne måten vil den bli evaluert i sin helhet, før den blir sammenlignet med 0 eller multiplisert med -1. Også hele makroen er omgitt av parenteser, for å hindre at den blir forurenset av annen kode. Hvis du ikke er forsiktig, risikerer du at kompilatoren feiltolker koden din.

på grunn av bivirkninger anses det som en svært dårlig ide å bruke makrofunksjoner som beskrevet ovenfor.

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

hvis ABSOLUTE_VALUE() var en reell funksjon ‘ x ‘ville nå ha verdien ‘-9’ , men fordi det var et argument i en makro det ble utvidet to ganger og dermed har en verdi på -8.

Eksempel:

for å illustrere farene ved makroer, vurder denne naive makroen

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

og koden

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

Ta en titt på dette og vurder hva verdien etter utførelse kan være. Uttalelsene blir omgjort til

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

Dermed etter utførelse i=8 og j=3 i stedet for det forventede resultatet av i = j = 8! Dette er grunnen til at du ble advart om å bruke et ekstra sett med parentes ovenfor, men selv med disse er veien full av farer. Varselleseren kan raskt innse at hvis aellerb inneholder uttrykk, må definisjonen parentes hver bruk av a, b i makrodefinisjonen, slik:

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

dette fungerer, forutsatt at a,b ikke har noen bivirkninger. Faktisk vil

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

resultere i k=4, i=3 og j=5. Dette ville være svært overraskende for alle som forventer MAX() å oppføre SEG som en funksjon.

så hva er den riktige løsningen? Løsningen er ikke å bruke makro i det hele tatt. En global, inline-funksjon, som denne

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

har ingen av fallgruvene ovenfor, men vil ikke fungere med alle typer.

MERK: den eksplisitteinline erklæringen er egentlig ikke nødvendig med mindre definisjonen er i en header fil, siden kompilatoren kan inline funksjoner for deg(med gcc dette kan gjøres med -finline-functions eller -O3). Kompilatoren er ofte bedre enn programmereren til å forutsi hvilke funksjoner som er verdt å inlining. Også funksjonssamtaler er ikke veldig dyre (de pleide å være).

kompilatoren er faktisk fri til å ignorereinline søkeord. Det er bare et hint (bortsett fra at inline er nødvendig for å tillate at en funksjon defineres i en header-fil uten å generere en feilmelding på grunn av at funksjonen er definert i mer enn en oversettelsesenhet).

(#,##)

operatorene # og ## brukes med #define-makroen. Bruk av # fører til at det første argumentet etter # returneres som en streng i anførselstegn. For eksempel vil kommandoen

#define as_string( s ) # s

gjøre kompilatoren snu denne kommandoen

puts( as_string( Hello World! ) ) ;

til

puts( "Hello World!" );

Ved å bruke ## sammenkobler hva som er før ## med hva som er etter det. For eksempel vil kommandoen

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

gjøre kompilatoren sving

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

til

printf( "%d", xy);

som selvfølgelig vil vise 10 til standard utgang.

det er mulig å sette sammen et makroargument med et konstant prefiks eller suffiks for å få en gyldig identifikator som i

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

som vil definere en funksjon kalt my_bar(). Men det er ikke mulig å integrere et makroargument i en konstant streng ved hjelp av sammenkoblingsoperatoren. FOR å oppnå en slik effekt kan MAN bruke ANSI c-egenskapen at to eller flere påfølgende strengkonstanter anses å være ekvivalente med en enkelt strengkonstant når de oppstår. Ved hjelp av denne egenskapen kan man skrive

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

som makroprosessoren vil bli til

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

som I sin tur vil bli tolket Av c-parseren som en enkelt strengkonstant.

følgende triks kan brukes til å slå en numerisk konstanter i strenglitteraler

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

dette er litt vanskelig, siden det er utvidet i 2 trinn. Førstnum2str(CONST) erstattes med str(23), som igjen erstattes med "23". Dette kan være nyttig i følgende eksempel:

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

Dette vil gi deg en fin feilsøkingsmelding, inkludert filen og linjen der meldingen ble utstedt. HVIS DEBUG ikke er definert, vil feilsøkingsmeldingen helt forsvinne fra koden din. Vær forsiktig så du ikke bruker denne typen konstruere med noe som har bivirkninger, siden dette kan føre til feil, som vises og forsvinner avhengig av kompilering parametere.

macrosEdit

Makroer er ikke typesjekket, så de evaluerer ikke argumenter. De adlyder heller ikke omfanget riktig, men tar bare strengen som er sendt til dem og erstatter hver forekomst av makroargumentet i makroens tekst med den faktiske strengen for den parameteren (koden kopieres bokstavelig talt til stedet den ble kalt fra).

et eksempel på hvordan du bruker en makro:

 #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; }

– resultatet av «a» skal være «2» (b + c = 16- > sendt TIL ADD- > 16 / SLICES – > resultatet er «2»)

merk:
det er vanligvis dårlig praksis å definere makroer i overskrifter.

en makro skal bare defineres når det ikke er mulig å oppnå det samme resultatet med en funksjon eller en annen mekanisme. Noen kompilatorer er i stand til å optimalisere kode til der samtaler til små funksjoner er erstattet med inline-kode, benektende enhver mulig hastighet fordel.Bruke typedefs, enums og inline (I C99) er ofte et bedre alternativ.En av de få situasjonene hvor inline-funksjoner ikke vil fungere – så du er ganske mye tvunget til å bruke funksjonslignende makroer i stedet – er å initialisere kompileringstidskonstanter (statisk initialisering av structs).Dette skjer når argumentene til makroen er bokstavelige som kompilatoren kan optimalisere til en annen bokstavelig.

#errorEdit

#feildirektivet stopper kompilering. Når man støter på standarden angir at kompilatoren skal avgi en diagnose som inneholder de resterende tokens i direktivet. Dette brukes mest til feilsøkingsformål.

Programmerere bruker «#error» inne i en betinget blokk, for å stoppe kompilatoren umiddelbart når «#if » eller «#ifdef» – i begynnelsen av blokken-oppdager et kompileringstidsproblem.Vanligvis hopper kompilatoren over blokken (og «#error» – direktivet inne i den) og samlingen fortsetter.

 #error message

#warningEdit

Mange kompilatorer støtter et #advarselsdirektiv. Når en oppstår, sender kompilatoren en diagnose som inneholder de resterende tokens i direktivet.

 #warning message

# undefEdit

#undef-direktivet undefinerer en makro. Identifikatoren trenger ikke å være definert tidligere.

#hvis,#else,#elif,#endif (conditionals)Edit

kommandoen #hvis kontrollerer om et kontrollerende betinget uttrykk evaluerer til null eller ikke null, og ekskluderer eller inkluderer en kodeblokk. For eksempel:

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

det betingede uttrykket kan inneholde En Hvilken Som Helst c-operatør med unntak av oppdragsoperatørene, inkrement-og reduksjonsoperatørene, operatørens adresse og operatorens størrelse.

en unik operator som brukes i forbehandling og ingen andre steder er den definerte operatoren. Den returnerer 1 hvis makronavnet, eventuelt vedlagt i parentes, er definert; 0 hvis ikke.

#endif-kommandoen avslutter en blokk startet av #if#ifdef eller #ifndef.

#elif-kommandoen ligner#if, bortsett fra at den brukes til å trekke ut en fra en serie kodeblokker. F. eks:

 #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

kommandoen # ifdef ligner #if, bortsett fra at kodeblokken som følger den, er valgt hvis et makronavn er definert. I denne forbindelse

#ifdef NAME

tilsvarer

#if defined NAME

#ifndef-kommandoen er lik #ifdef, bortsett fra at testen er reversert:

#ifndef NAME

tilsvarer

#if !defined NAME

#lineedit

dette preprosessordirektivet brukes til å sette filnavnet og linjenummeret til linjen etter direktivet til nye verdier. Dette brukes til å angi makroene __FILE__ OG __LINE__.

Legg igjen en kommentar

Din e-postadresse vil ikke bli publisert.