C Programming / Preprocessor directives en macro ‘ s

Directives zijn speciale instructies gericht aan de preprocessor (preprocessor directive) of aan de compiler (compiler directive) over hoe het een deel of het geheel van uw broncode moet verwerken of een aantal vlaggen op het uiteindelijke object moet zetten en worden gebruikt om het schrijven van broncode gemakkelijker te maken (meer draagbaar bijvoorbeeld) en om de broncode begrijpelijker te maken. Richtlijnen worden afgehandeld door de preprocessor, die ofwel een afzonderlijk programma is dat door de compiler wordt aangeroepen of een deel van de compiler zelf.

# includeEdit

C heeft een aantal functies als onderdeel van de taal en een aantal andere als onderdeel van een standaardbibliotheek, wat een repository van code is die beschikbaar is naast elke standaard-conformante C-compiler. Wanneer de C compiler je programma compileert, wordt het meestal ook gekoppeld aan de standaard C bibliotheek. Als u bijvoorbeeld een #include <stdio.h> richtlijn tegenkomt, vervangt deze de richtlijn door de inhoud van het stdio.h header bestand.

wanneer u functies uit de bibliotheek gebruikt, vereist C dat u aangeeft wat u zou gebruiken. De eerste regel in het programma is een preprocessing directive die er als volgt uit zou moeten zien:

#include <stdio.h>

de bovenstaande regel veroorzaakt de C declaraties die zich in de stdio bevinden.h header moet worden opgenomen voor gebruik in uw programma. Meestal wordt dit uitgevoerd door gewoon invoegen in uw programma de inhoud van een header-bestand genaamd stdio.h, gelegen op een systeemafhankelijke locatie. De locatie van dergelijke bestanden kan worden beschreven in de documentatie van uw compiler. Een lijst van standaard C header bestanden is hieronder in de headers tabel.

het stdio.h header bevat verschillende declaraties voor input / output (I/O) met behulp van een abstractie van I/O mechanismen genaamd streams. Er is bijvoorbeeld een output stream object genaamd stdout dat wordt gebruikt om tekst uit te voeren naar de standaard uitvoer, die meestal de tekst op het computerscherm weergeeft.

bij gebruik van hoekhaken zoals het voorbeeld hierboven, wordt de preprocessor geïnstrueerd om te zoeken naar het include-bestand langs het ontwikkelomgeving pad voor de standaard includes.

#include "other.h"

Als u aanhalingstekens ( “” ) gebruikt, wordt van de preprocessor verwacht dat hij in een aantal extra, meestal door de gebruiker gedefinieerde, locaties zoekt naar het headerbestand, en alleen terugvalt naar de standaard include paths als het niet in die extra locaties wordt gevonden. Het is gebruikelijk dat dit formulier zoeken in dezelfde directory bevat als het bestand dat de #include-richtlijn bevat.

Opmerking: U dient de documentatie van de ontwikkelomgeving te controleren die u gebruikt voor alle leveranciersspecifieke implementaties van de #include-richtlijn.

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

De pragma (pragmatic information) richtlijn maakt deel uit van de standaard, maar de Betekenis van elke pragma hangt af van de software-implementatie van de standaard die wordt gebruikt. De # pragma instructie biedt een manier om speciaal gedrag van de compiler aan te vragen. Deze richtlijn is het nuttigst voor programma ‘ s die ongewoon groot zijn of die gebruik moeten maken van de mogelijkheden van een bepaalde compiler.

Pragmas worden gebruikt binnen het bronprogramma.

#pragma token(s)
  1. pragma wordt meestal gevolgd door een enkele token, die een commando vertegenwoordigt voor de compiler om te gehoorzamen. U moet de software-implementatie van de C-standaard controleren die u wilt gebruiken voor een lijst van de ondersteunde tekenen. Het is niet verrassend dat de set commando ’s die in #pragma directives kunnen verschijnen voor elke compiler verschillend is; je moet de documentatie voor je compiler raadplegen om te zien welke commando’ s het toestaat en wat die commando ‘ s doen.

bijvoorbeeld een van de meest geà mplementeerde preprocessor-richtlijnen, #pragma once wanneer deze aan het begin van een header-bestand wordt geplaatst, geeft aan dat het bestand waarin het zich bevindt, zal worden overgeslagen als de preprocessor meerdere keren wordt opgenomen.

Opmerking: Er bestaan andere methoden om deze actie uit te voeren, die gewoonlijk wordt aangeduid met onder andere guards.

#defineEdit

waarschuwing: Preprocessor macro ‘ s, hoewel verleidelijk, kunnen vrij onverwachte resultaten opleveren als ze niet goed gedaan worden. Houd er altijd rekening mee dat macro ‘ s tekstuele substituties zijn die gedaan worden aan je broncode voordat er iets gecompileerd wordt. De compiler weet niets over de macro ‘ s en krijgt ze nooit te zien. Dit kan obscure fouten veroorzaken, onder andere negatieve effecten. Geef de voorkeur aan taalfuncties, als deze gelijkwaardig zijn (gebruik bijvoorbeeld const int of enum in plaats van #defined constanten).

Dat gezegd hebbende, zijn er gevallen waarin macro ‘ s zeer nuttig zijn (zie de debug macro hieronder voor een voorbeeld).

de # define-richtlijn wordt gebruikt om waarden of macro ‘ s te definiëren die door de preprocessor worden gebruikt om de programmabroncode te manipuleren voordat deze wordt gecompileerd. Omdat preprocessor definities worden vervangen voordat de compiler handelt op de broncode, zijn eventuele fouten die worden geïntroduceerd door # define moeilijk te traceren.

volgens afspraak worden waarden gedefinieerd met # define in hoofdletters genoemd. Hoewel dit geen vereiste is, wordt het als een zeer slechte praktijk beschouwd om anders te doen. Hierdoor kunnen de waarden gemakkelijk worden geïdentificeerd bij het lezen van de broncode.

vandaag wordt # define voornamelijk gebruikt om compiler-en platformverschillen af te handelen. Bijvoorbeeld, een define kan een constante bevatten die de juiste foutcode is voor een systeemaanroep. Het gebruik van # define zou dus beperkt moeten zijn, tenzij het absoluut noodzakelijk is; typedef-statements en constante variabelen kunnen vaak dezelfde functies veiliger uitvoeren.

een andere eigenschap van het #define commando is dat het argumenten kan aannemen, waardoor het vrij nuttig is als een pseudo-functie Schepper. Overweeg de volgende code:

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

Het is over het algemeen een goed idee om extra haakjes te gebruiken bij het gebruik van complexe macro ‘ s. Merk op dat in het bovenstaande voorbeeld de variabele “x” altijd binnen zijn eigen verzameling haakjes ligt. Op deze manier wordt het in zijn geheel geëvalueerd, voordat het wordt vergeleken met 0 of vermenigvuldigd met -1. Ook is de gehele macro omgeven door haakjes, om te voorkomen dat deze door andere code wordt besmet. Als je niet oppast, loop je het risico dat de compiler je code verkeerd interpreteert.

vanwege bijwerkingen wordt het als een zeer slecht idee beschouwd om macro functies te gebruiken zoals hierboven beschreven.

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

als ABSOLUTE_VALUE() een echte functie was, zou ‘x’ nu de waarde ‘-9’ hebben, maar omdat het een argument in een macro was, werd het tweemaal uitgebreid en heeft het dus een waarde van -8.

voorbeeld:

om de gevaren van macro ‘ s te illustreren, overweeg deze naïeve macro

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

en de code

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

kijk hier eens naar en overweeg wat de waarde na het uitvoeren zou kunnen zijn. De statements worden omgezet in

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

dus, na uitvoering i=8 en j=3 in plaats van het verwachte resultaat van I=j=8! Dit is de reden waarom u werd gewaarschuwd om een extra set haakjes hierboven te gebruiken, maar zelfs met deze, de weg is beladen met gevaren. De waarschuwingslezer kan zich snel realiseren dat als a of b uitdrukkingen bevat, de definitie elk gebruik van a,b in de macrodefinitie tussen haakjes moet zetten, als volgt:

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

Dit werkt, mits a,b geen bijwerkingen hebben.

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

zou resulteren in k=4, i=3 en j=5. Dit zou zeer verrassend zijn voor iedereen die verwacht dat MAX () zich gedraagt als een functie.

dus wat is de juiste oplossing? De oplossing is om helemaal geen macro te gebruiken. Een globale, inline functie, zoals deze

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

heeft geen van de valkuilen hierboven, maar zal niet met alle typen werken.

opmerking: de expliciete inline declaratie is niet echt nodig, tenzij de definitie zich in een headerbestand bevindt, omdat uw compiler functies voor u kan inline (met gcc kan dit worden gedaan met -finline-functions of -O3). De compiler is vaak beter dan de programmeur in het voorspellen welke functies de moeite waard zijn om in te schakelen. Ook, functie gesprekken zijn niet echt duur (ze waren vroeger).

de compiler is eigenlijk vrij om het inline sleutelwoord te negeren. Het is slechts een hint (behalve dat inline nodig is om een functie in een headerbestand te kunnen definiëren zonder een foutmelding te genereren omdat de functie in meer dan één vertaaleenheid wordt gedefinieerd).

(#,##)

de # en ## operators worden gebruikt met de #define macro. Het gebruik van # zorgt ervoor dat het eerste argument na de # wordt geretourneerd als een tekenreeks tussen aanhalingstekens. Bijvoorbeeld, het commando

#define as_string( s ) # s

zorgt ervoor dat de compiler dit commando

puts( as_string( Hello World! ) ) ;

omzet in

puts( "Hello World!" );

met behulp van ## verbindt wat er voor de ## met wat er achter zit. Bijvoorbeeld, het commando

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

zal de compiler draaien

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

naar

printf( "%d", xy);

die, natuurlijk, 10 tot standaard uitvoer zal weergeven.

Het is mogelijk om een macroargument samen te voegen met een constant voorvoegsel of achtervoegsel om een geldige identifier te verkrijgen zoals in

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

die een functie genaamd my_bar () definieert. Maar het is niet mogelijk om een macro argument te integreren in een constante string met behulp van de concatenation operator. Om een dergelijk effect te verkrijgen, kan men de eigenschap ANSI C gebruiken dat twee of meer opeenvolgende stringconstanten worden beschouwd als gelijkwaardig aan een enkele stringconstante wanneer ze worden aangetroffen. Met behulp van deze eigenschap kan men

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

schrijven die de macro-processor zal veranderen in

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

die op zijn beurt door de C-parser zal worden geïnterpreteerd als een enkele stringconstante.

de volgende truc kan worden gebruikt om numerieke constanten om te zetten in tekenreeksletters

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

Dit is een beetje lastig, omdat het in 2 stappen wordt uitgebreid. Eerst wordt num2str(CONST) vervangen door str(23), die op zijn beurt wordt vervangen door "23". Dit kan nuttig zijn in het volgende voorbeeld:

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

Dit geeft u een mooi debug bericht inclusief het bestand en de regel waar het bericht werd uitgegeven. Als DEBUG is niet gedefinieerd maar de debugging bericht zal volledig verdwijnen uit uw code. Wees voorzichtig om dit soort constructie niet te gebruiken met iets dat bijwerkingen heeft, omdat dit kan leiden tot bugs, die verschijnen en verdwijnen afhankelijk van de compilatie parameters.

macrosEdit

macro ‘ s zijn niet type-gecontroleerd en dus evalueren ze argumenten niet. Ook gehoorzamen ze scope niet goed, maar nemen gewoon de string mee die aan hen is doorgegeven en vervangen elk voorkomen van het macroargument in de tekst van de macro door de werkelijke string voor die parameter (de code wordt letterlijk gekopieerd naar de locatie waar het vandaan werd aangeroepen).

Een voorbeeld van het gebruik van een macro:

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

— het resultaat van “een” moet “2” (b + c = 16 -> doorgegeven aan TOEVOEGEN -> 16 / PLAKJES -> resultaat is “2”)

OPMERKING:
Het is meestal een slechte gewoonte te definieer macro ‘ s in de headers.

een macro moet alleen worden gedefinieerd als het niet mogelijk is hetzelfde resultaat met een functie of een ander mechanisme te bereiken. Sommige compilers zijn in staat om code te optimaliseren waar oproepen naar kleine functies worden vervangen door inline code, waardoor elk mogelijk snelheidsvoordeel wordt genegeerd.Het gebruik van typedefs, enums en inline (in C99) is vaak een betere optie.

een van de weinige situaties waarin inline functies niet zullen werken — dus je bent vrijwel gedwongen om in plaats daarvan functie-achtige macro ’s te gebruiken — is het initialiseren van compilatietijdconstanten (statische initialisatie van struct’ s).Dit gebeurt wanneer de argumenten voor de macro literals zijn die de compiler kan optimaliseren naar een andere letterlijke.

#errorredit

De #error directive stopt de compilatie. Wanneer men tegenkomt de norm specificeert dat de compiler een diagnose moet uitzenden die de resterende tokens in de richtlijn bevat. Dit wordt meestal gebruikt voor debugging doeleinden.

programmeurs gebruiken “# error “in een conditioneel blok, om de compiler onmiddellijk te stoppen wanneer” # if “of” #ifdef ” — aan het begin van het blok — een compilatietijdprobleem detecteert.Normaal slaat de compiler het blok over (en de “#error” richtlijn erin) en de compilatie gaat verder.

 #error message

#warningEdit

veel compilers ondersteunen een #warning directive. Wanneer men tegenkomt, zendt de compiler een diagnose uit die de resterende tokens in de richtlijn bevat.

 #warning message

#undefEdit

De #undef-richtlijn undefinieert een macro. De identificatiecode hoeft niet vooraf gedefinieerd te zijn.

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

het #if Commando controleert of een controlerende voorwaardelijke expressie evalueert tot nul of niet-nul, en sluit een blok code uit of bevat. Bijvoorbeeld:

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

de conditionele uitdrukking kan elke C-operator bevatten, met uitzondering van de toegewezen operators, de increment-en decrementoperators, het adres-van operator en de sizeof operator.

een unieke operator die wordt gebruikt in voorbewerking en nergens anders is de gedefinieerde operator. Het geeft 1 terug als de macronaam, optioneel tussen haakjes, momenteel is gedefinieerd; 0 als dat niet het geval is.

het # endif-Commando beëindigt een blok dat wordt gestart met #if#ifdef, of #ifndef.

het #elif commando is vergelijkbaar met #if, behalve dat het wordt gebruikt om er een uit een reeks blokken code te extraheren. Bijvoorbeeld:

 #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

het #ifdef commando is vergelijkbaar met #if, behalve dat het codeblok dat erop volgt is geselecteerd als een macronaam is gedefinieerd. In dit opzicht

#ifdef NAME

is gelijk aan

#if defined NAME

De #ifndef opdracht is vergelijkbaar met #ifdef, behalve dat de test wordt teruggenomen:

#ifndef NAME

is gelijk aan

#if !defined NAME

#lineEdit

Deze preprocessor richtlijn wordt gebruikt om de bestandsnaam en het regelnummer van de regel die volgt op de richtlijn naar nieuwe waarden. Dit wordt gebruikt om de __FILE__ en __LINE__ macro ‘ s in te stellen.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.