C Programmering/Præprocessordirektiver og makroer

direktiver er specielle instruktioner rettet mod præprocessoren (præprocessordirektivet) eller til kompilatoren (compiler-direktivet) om, hvordan det skal behandle en del af eller hele din kildekode eller indstille nogle flag på det endelige objekt og bruges til at gøre det lettere at skrive kildekode (mere bærbar for eksempel) og for at gøre kildekoden mere forståelig. Direktiver håndteres af præprocessoren, som enten er et separat program, der påberåbes af kompilatoren eller en del af kompilatoren selv.

#includeEdit

C har nogle funktioner som en del af sproget og nogle andre som en del af et standardbibliotek, som er et arkiv med kode, der er tilgængeligt sammen med enhver Standardkonform C-kompilator. Når C compiler kompilerer dit program, forbinder det normalt også det med standard C-biblioteket. For eksempel, når du støder på et#include <stdio.h> direktiv, erstatter det direktivet med indholdet af stdio.h header fil.

Når du bruger funktioner fra biblioteket, kræver C, at du erklærer, hvad du vil bruge. Den første linje i programmet er et forbehandlingsdirektiv, der skal se sådan ud:

#include <stdio.h>

ovenstående linje forårsager C-deklarationerne, der er i stdio.h header, der skal medtages til brug i dit program. Normalt er dette gennemføres ved blot at indsætte i dit program indholdet af en header fil kaldet stdio.h, placeret i et systemafhængigt sted. Placeringen af sådanne filer kan beskrives i din kompilators dokumentation. En liste over standard C header-filer er angivet nedenfor i tabellen overskrifter.

stdio.h header indeholder forskellige erklæringer for input/output (I/O) ved hjælp af en abstraktion af I / O-mekanismer kaldet streams. For eksempel er der et outputstrømobjekt kaldet stdout, som bruges til at udsende tekst til standardudgangen, som normalt viser teksten på computerskærmen.

Hvis du bruger vinkelbeslag som eksemplet ovenfor, instrueres præprocessoren i at søge efter Inkluder-filen langs udviklingsmiljøstien for standarden inkluderer.

#include "other.h"

Hvis du bruger anførselstegn (” “), forventes præprocessoren at søge i nogle yderligere, normalt brugerdefinerede, placeringer for overskriftsfilen og kun falde tilbage til standarden inkluderer stier, hvis den ikke findes på disse yderligere placeringer. Det er almindeligt, at denne formular inkluderer søgning i den samme mappe som filen, der indeholder #Inkluder direktivet.

Bemærk: Du bør kontrollere dokumentationen for det udviklingsmiljø, du bruger til eventuelle leverandørspecifikke implementeringer af #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 fhænger af programmel implementering af den standard, der anvendes. # Pragma-direktivet giver en måde at anmode om særlig adfærd fra kompilatoren. Dette direktiv er mest nyttigt for programmer, der er usædvanligt store, eller som skal udnytte kapaciteten hos en bestemt kompilator.

Pragmas bruges i kildeprogrammet.

#pragma token(s)
  1. pragma efterfølges normalt af et enkelt token, som repræsenterer en kommando for kompilatoren at adlyde. Du bør kontrollere programmel implementering af C-standarden, du har til hensigt at bruge til en liste over de understøttede tokens. Ikke overraskende er det sæt kommandoer, der kan vises i #pragma-direktiver, forskelligt for hver compiler; du bliver nødt til at konsultere dokumentationen til din compiler for at se, hvilke kommandoer det tillader, og hvad disse kommandoer gør.

for eksempel et af de mest implementerede præprocessordirektiver,#pragma once når den placeres i begyndelsen af en overskriftsfil, angiver, at den fil, hvor den befinder sig, vil blive sprunget over, hvis den medtages flere gange af præprocessoren.

Bemærk: Der findes andre metoder til at udføre denne handling, der ofte kaldes ved hjælp af Inkluder vagter.

#defineEdit

advarsel: Præprocessormakroer, selvom de er fristende, kan producere ganske uventede resultater, hvis de ikke gøres rigtigt. Husk altid, at makroer er tekstmæssige udskiftninger gjort til din kildekode, før noget er kompileret. Kompilatoren ved ikke noget om makroerne og får aldrig at se dem. Dette kan give uklare fejl, blandt andre negative virkninger. Foretrækker at bruge sprogfunktioner, hvis der er ækvivalente (i eksempel brug const int eller enum i stedet for #defined konstanter).

når det er sagt, er der tilfælde, hvor makroer er meget nyttige (se debug Makro nedenfor for et eksempel).

#define-direktivet bruges til at definere værdier eller makroer, der bruges af præprocessoren til at manipulere programkildekoden, før den kompileres. Da præprocessordefinitioner erstattes, før kompilatoren virker på kildekoden, er eventuelle fejl, der introduceres af #define, vanskelige at spore.

efter konvention er værdier defineret ved hjælp af #define navngivet med store bogstaver. Selvom det ikke er et krav, betragtes det som meget dårlig praksis at gøre andet. Dette gør det nemt at identificere værdierne, når du læser kildekoden.

i dag er #define primært brugt til at håndtere compiler og platform forskelle. F. eks.kan en definere indeholde en konstant, som er den relevante fejlkode for et systemopkald. Brugen af # define bør således begrænses, medmindre det er absolut nødvendigt; typedef-udsagn og konstante variabler kan ofte udføre de samme funktioner mere sikkert.

et andet træk ved kommandoen #Definer er, at det kan tage argumenter, hvilket gør det ret nyttigt som en pseudofunktionsskaber. Overvej følgende kode:

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

det er generelt en god ide at bruge ekstra parenteser, når du bruger komplekse makroer. Bemærk, at i ovenstående eksempel er variablen “h” altid inden for sit eget sæt parenteser. På denne måde evalueres det i sin helhed, inden det sammenlignes med 0 eller ganges med -1. Hele makroen er også omgivet af parenteser for at forhindre, at den bliver forurenet af anden kode. Hvis du ikke er forsigtig, risikerer du, at kompilatoren fejlagtigt fortolker din kode.

på grund af bivirkninger betragtes det som en meget dårlig ide at bruge makrofunktioner som beskrevet ovenfor.

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

Hvis ABSOLUTE_VALUE() var en reel funktion ‘S’ ville nu have værdien ‘-9’, men fordi det var et argument i en makro, blev det Udvidet to gange og har således en værdi på -8.

eksempel:

for at illustrere farerne ved makroer skal du overveje denne naive Makro

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

og koden

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

se på dette og overvej, hvad værdien efter udførelse kan være. Udsagnene omdannes til

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

således efter udførelse i=8 og j=3 i stedet for det forventede resultat af i=j=8! Derfor blev du advaret om at bruge et ekstra sæt parentes ovenfor, men selv med disse er vejen fyldt med farer. Alarmlæseren kan hurtigt indse, at hvis a eller b indeholder udtryk,skal definitionen parentes hver brug af A, b i makrodefinitionen som denne:

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

Dette fungerer, forudsat at A,b ikke har nogen bivirkninger. Faktisk

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

ville resultere i k=4, i=3 og j=5. Dette ville være meget overraskende for alle, der forventer, at maks () opfører sig som en funktion.

så hvad er den rigtige løsning? Løsningen er slet ikke at bruge makro. En global, inline-funktion som denne

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

har ingen af faldgruberne ovenfor, men fungerer ikke med alle typer.

bemærk: den eksplicitteinline erklæring er ikke rigtig nødvendig, medmindre definitionen er i en headerfil, da din kompilator kan inline funktioner for dig (med gcc kan dette gøres med-finline-functions eller-O3). Kompilatoren er ofte bedre end programmøren til at forudsige, hvilke funktioner der er værd at inline. Funktionsopkald er heller ikke rigtig dyre (de plejede at være).

kompilatoren er faktisk fri til at ignorereinline søgeord. Det er kun et tip (bortset fra at inline er nødvendigt for at tillade en funktion at blive defineret i en headerfil uden at generere en fejlmeddelelse på grund af at funktionen er defineret i mere end en oversættelsesenhed).

(#,##)

# og ## operatorerne bruges sammen med #Definer makroen. Brug af # får det første argument efter # til at blive returneret som en streng i anførselstegn. For eksempel kommandoen

#define as_string( s ) # s

får kompilatoren til at dreje denne kommando

puts( as_string( Hello World! ) ) ;

til

puts( "Hello World!" );

Ved hjælp af ## sammenkæder hvad der er før ## med hvad der er efter det. For eksempel kommandoen

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

får kompilatoren til at dreje

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

til

printf( "%d", xy);

som naturligvis viser 10 til standardudgang.

det er muligt at sammenkæde et makroargument med et konstant præfiks eller suffiks for at få en gyldig identifikator som i

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

som definerer en funktion kaldet my_bar(). Men det er ikke muligt at integrere et makroargument i en konstant streng ved hjælp af sammenkædningsoperatoren. For at opnå en sådan effekt kan man bruge ANSI C-egenskaben, at to eller flere på hinanden følgende strengkonstanter betragtes som ækvivalente med en enkelt strengkonstant, når de opstår. Ved hjælp af denne egenskab kan man skrive

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

som makroprocessoren bliver til

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

som igen vil blive fortolket af C-parseren som en enkelt strengkonstant.

følgende trick kan bruges til at omdanne en numerisk konstanter til strenglitteraler

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

Dette er lidt vanskeligt, da det udvides i 2 trin. Førstnum2str(CONST) erstattes medstr(23), som igen erstattes med"23". Dette kan være nyttigt i følgende eksempel:

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

Dette giver dig en dejlig debug-meddelelse inklusive filen og linjen, hvor meddelelsen blev udstedt. Hvis DEBUG ikke er defineret dog debugging besked vil helt forsvinde fra din kode. Pas på ikke at bruge denne form for konstruktion med noget, der har bivirkninger, da dette kan føre til fejl, der vises og forsvinder afhængigt af kompileringsparametrene.

macrosEdit

makroer er ikke typekontrolleret, og derfor evaluerer de ikke argumenter. De adlyder heller ikke omfanget korrekt, men tager blot strengen, der er sendt til dem, og erstatter hver forekomst af makroargumentet i makroens tekst med den faktiske streng for den parameter (koden kopieres bogstaveligt talt til det sted, den blev kaldt fra).

et eksempel på, hvordan man bruger 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 af “A” skal være “2” (b + c = 16- > sendt til ADD- > 16/skiver- > resultatet er “2”)

Bemærk:
det er normalt dårlig praksis at definere makroer i overskrifter.

en makro bør kun defineres, når det ikke er muligt at opnå det samme resultat med en funktion eller en anden mekanisme. Nogle kompilatorer er i stand til at optimere kode, hvor opkald til små funktioner erstattes med inline-kode, hvilket negerer enhver mulig hastighedsfordel.Brug af typedefs, enums og inline (i C99) er ofte en bedre mulighed.

en af de få situationer, hvor inline-funktioner ikke fungerer-så du er stort set tvunget til at bruge funktionslignende makroer i stedet-er at initialisere kompileringstidskonstanter (statisk initialisering af strukturer).Dette sker, når argumenterne til makroen er bogstavelige, som kompilatoren kan optimere til en anden bogstavelig.

#errorredit

#fejldirektivet stopper kompilering. Når man støder på, specificerer standarden, at kompilatoren skal udsende en diagnose, der indeholder de resterende tokens i direktivet. Dette bruges mest til debugging formål.

programmører bruger ” # error “inde i en betinget blok for straks at standse kompilatoren, når” #if “eller” #ifdef ” -i begyndelsen af blokken-registrerer et kompileringstidsproblem.Normalt springer kompilatoren over blokken (og” # error ” – direktivet inde i det), og kompileringen fortsætter.

 #error message

#advarsel

mange kompilatorer understøtter et #advarselsdirektiv. Når man støder på, udsender kompilatoren en diagnose, der indeholder de resterende tokens i direktivet.

 #warning message

#undefEdit

#undef-direktivet undefinerer en makro. Identifikatoren behøver ikke tidligere at være defineret.

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

kommandoen #if kontrollerer, om et kontrollerende betinget udtryk evalueres til nul eller ikke-nul og udelukker eller inkluderer en blok kode henholdsvis. For eksempel:

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

det betingede udtryk kan indeholde enhver c-operatør bortset fra tildelingsoperatørerne, forøgelses-og reduktionsoperatørerne, operatørens adresse og størrelsen af operatøren.

en unik operatør, der bruges til forbehandling, og ingen andre steder er den definerede operatør. Det returnerer 1, hvis makronavnet, eventuelt vedlagt i parentes, i øjeblikket er defineret; 0 hvis ikke.

kommandoen #endif slutter en blok startet af#if#ifdef, eller#ifndef.

kommandoen #elif svarer til#if, bortset fra at den bruges til at udtrække en fra en række kodeblokke. 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 svarer til#if, bortset fra at kodeblokken efter den er valgt, hvis et Makronavn er defineret. I denne henseende

#ifdef NAME

svarer til

#if defined NAME

kommandoen #ifndef svarer til #ifdef, bortset fra at testen vendes:

#ifndef NAME

svarer til

#if !defined NAME

#LINEEDIT

dette præprocessordirektiv bruges til at indstille filnavnet og linjenummeret på linjen efter direktivet til nye værdier. Dette bruges til at indstille __fil__ og __linje__ makroer.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.