C-ohjelmointi – / Esiprosessoridirektiivit ja makrot

direktiivit ovat esiprosessorille (esiprosessoridirektiivi) tai kääntäjälle (kääntäjädirektiivi) suunnattuja erityisohjeita siitä, miten lähdekoodisi tulisi käsitellä osittain tai kokonaan tai asettaa joitakin lippuja lopulliseen objektiin, ja niitä käytetään helpottamaan lähdekoodin kirjoittamista (esimerkiksi kannettavampaa) ja tekemään lähdekoodista ymmärrettävämpää. Direktiivejä käsittelee esiprosessori, joka on joko kääntäjän kutsuma erillinen ohjelma tai kääntäjän itsensä osa.

#includeEdit

C: ssä on joitain ominaisuuksia osana kieltä ja joitain muita osana standardikirjastoa, joka on jokaisen standardia noudattavan C-kääntäjän rinnalla saatavilla oleva koodivarasto. Kun C-kääntäjä kokoaa ohjelmasi, se yleensä myös liittää sen tavalliseen C-kirjastoon. Esimerkiksi kohdatessaan #include <stdio.h> direktiivi korvaa direktiivin stdion sisällöllä.h-otsikkotiedosto.

kun käytät kirjaston ominaisuuksia, C vaatii sinua ilmoittamaan, mitä käyttäisit. Ohjelman ensimmäinen rivi on esikäsittelyohje, jonka pitäisi näyttää tältä:

#include <stdio.h>

yllä oleva rivi aiheuttaa stdiossa olevat C-julistukset.h header sisällytetään käytettäväksi ohjelmassa. Yleensä tämä toteutetaan vain lisäämällä ohjelmaasi stdio-nimisen otsikkotiedoston sisältö.h, joka sijaitsee järjestelmästä riippuvaisessa paikassa. Tällaisten tiedostojen sijainti voidaan kuvata kääntäjän dokumentaatiossa. Luettelo standardeista C-otsikkotiedostoista on alla Otsaketaulukossa.

stdio.h header sisältää erilaisia ilmoituksia input / output (I/O) käyttäen abstraktio I/O mekanismeja kutsutaan virtoja. Esimerkiksi on olemassa stdout-niminen ulostulovirtaobjekti, jota käytetään tekstin tulostamiseen vakiotulosteeseen, joka yleensä näyttää tekstin tietokoneen näytöllä.

Jos käytetään kulmasulkeita kuten yllä olevassa esimerkissä, esiprosessoria neuvotaan etsimään include-tiedosto kehitysympäristön polun kautta standardiin sisältyy.

#include "other.h"

Jos käytät lainausmerkkejä (” ”), esiprosessorin odotetaan etsivän joistakin, yleensä käyttäjän määrittelemistä lisäpaikoista otsikkotiedostolle ja palaavan standardiin sisältävät polut vain, jos sitä ei löydy kyseisistä lisäpaikoista. On tavallista, että tämä lomake sisältää haun samasta hakemistosta kuin tiedosto, joka sisältää #include directive.

Huomaa: Tarkista käyttämäsi kehitysympäristön dokumentointi kaikista #include-direktiivin toimittajakohtaisista toteutuksista.

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

pragmadirektiivi on osa standardia, mutta minkä tahansa pragmadirektiivin merkitys riippuu käytetyn standardin ohjelmistototeutus. #Pragma-direktiivi tarjoaa tavan pyytää erityistä käyttäytymistä kääntäjältä. Tämä direktiivi on hyödyllisin ohjelmille, jotka ovat epätavallisen suuria tai joiden on hyödynnettävä tietyn kääntäjän ominaisuuksia.

pragmoja käytetään source-ohjelmassa.

#pragma token(s)
  1. pragmaa seuraa yleensä yksi token, joka merkitsee käskyä kääntäjälle totella. Sinun pitäisi tarkistaa ohjelmiston täytäntöönpanoa C standardin aiot käyttää luettelon tuetuista tokens. Ei ole yllättävää, joukko komentoja, jotka voivat näkyä #pragma direktiivit on erilainen kunkin kääntäjä; sinun täytyy tarkistaa asiakirjat kääntäjä nähdä, mitkä komennot se sallii ja mitä nämä komennot tehdä.

esimerkiksi yksi toteutetuimmista esiprosessoridirektiiveistä, #pragma once kun se sijoitetaan otsikkotiedoston alkuun, osoittaa, että tiedosto, jossa se sijaitsee, ohitetaan, jos esiprosessori sisällyttää sen useita kertoja.

huomautus: Tämän toiminnon tekemiseen on olemassa muita menetelmiä, joita yleisesti kutsutaan muun muassa vartijoiden käytöksi.

#defineEdit

varoitus: Esiprosessorimakrot, vaikkakin houkuttelevat, voivat tuottaa varsin odottamattomia tuloksia, jos niitä ei tehdä oikein. Muista aina, että makrot ovat tekstimuotoisia korvikkeita, jotka tehdään lähdekoodillesi ennen kuin mitään kootaan. Kääntäjä ei tiedä mitään makroista eikä koskaan pääse näkemään niitä. Tämä voi tuottaa hämäriä virheitä, muun muassa kielteisiä vaikutuksia. Käytä mieluummin kielen ominaisuuksia, jos vastaavia on (esimerkissä käytetään const int tai enum sijaan #defined vakioita).

on tapauksia, joissa makrot ovat erittäin hyödyllisiä (katso debug makro alla esimerkkinä).

#define-direktiiviä käytetään määrittelemään arvoja tai makroja, joita esiprosessori käyttää manipuloidakseen ohjelman lähdekoodia ennen sen kokoamista. Koska esikäsittelijämäärittelyt korvataan ennen kuin kääntäjä vaikuttaa lähdekoodiin, #definen tuomia virheitä on vaikea jäljittää.

käytännön mukaan #define-merkillä määritellyt arvot on nimetty isoilla kirjaimilla. Vaikka näin ei vaadita, pidetään erittäin huonona käytäntönä toimia toisin. Näin arvot voidaan helposti tunnistaa lähdekoodia lukiessa.

nykyään #defineä käytetään ensisijaisesti kääntäjän ja Alustan erojen käsittelyyn. Esim. määrittelyssä saattaa olla vakio, joka on järjestelmäkutsun asianmukainen virhekoodi. #Definen käyttöä tulisikin rajoittaa, ellei se ole aivan välttämätöntä; typedef-lauseet ja vakiomuuttujat voivat usein suorittaa samat toiminnot turvallisemmin.

toinen #define-komennon ominaisuus on se, että se voi kestää argumentteja, mikä tekee siitä varsin hyödyllisen pseudofunktioiden luojana. Harkitsehan seuraavaa koodia:

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

on yleensä hyvä käyttää ylimääräisiä sulkeita käytettäessä monimutkaisia makroja. Huomaa, että yllä olevassa esimerkissä muuttuja ”x” on aina Oman sulkeisjoukkonsa sisällä. Näin se arvioidaan kokonaisena, ennen kuin sitä verrataan arvoon 0 tai kerrotaan luvulla -1. Lisäksi koko makro on ympäröity sulkeilla, jotta se ei saastuisi muusta koodista. Jos et ole varovainen, vaarana on, että kääntäjä tulkitsee koodisi väärin.

sivuvaikutusten vuoksi edellä kuvattujen makrofunktioiden käyttöä pidetään erittäin huonona ideana.

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

Jos ABSOLUTE_ARVO() olisi reaalifunktio ”x”, sen arvo olisi nyt ”-9”, mutta koska se oli argumentti makrossa, sitä laajennettiin kahdesti ja sen arvo on siten -8.

esimerkki:

havainnollistaa makrojen vaarallisuutta tarkastelemalla tätä naiivia makroa

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

ja koodia

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

katso tätä ja mieti, mikä arvo suorituksen jälkeen voisi olla. Lausumat muutetaan muotoon

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

näin toteutuksen jälkeen i=8 ja j=3 odotetun tuloksen sijaan I=J=8! Tämän vuoksi sinua kehotettiin käyttämään ylimääräisiä sulkuja edellä, mutta niistäkin huolimatta tie on täynnä vaaroja. Valpas lukija saattaa nopeasti tajuta, että jos a tai b sisältää ilmaisuja, määritelmän on sulkeuduttava jokaiseen käyttöön a,b makromääritelmässä näin:

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

tämä toimii, edellyttäen että A, b: llä ei ole sivuvaikutuksia.

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

johtaisi K=4, i=3 ja j=5. Tämä olisi erittäin yllättävää kaikille, jotka odottavat Maxin () käyttäytyvän funktion tavoin.

mikä siis on oikea ratkaisu? Ratkaisu on olla käyttämättä makroa lainkaan. Tämän kaltaisella yleisellä, inline-funktiolla

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

ei ole mitään yllä olevista sudenkuopista, mutta se ei toimi kaikilla tyypeillä.

huomautus: eksplisiittinen inline ilmoitus ei ole varsinaisesti tarpeen, ellei määritelmä ole otsikkotiedostossa, koska kääntäjä voi tehdä inline-funktioita sinulle (GCC: llä tämä voidaan tehdä -finline-functions tai -O3). Kääntäjä on usein ohjelmoijaa parempi ennustamaan, mitkä funktiot kannattaa inline. Myös funktiopuhelut eivät ole todella kalliita (ne olivat ennen).

Kääntäjä on itse asiassa vapaa jättämään inline avainsanan huomiotta. Se on vain vihje (paitsi että inline on tarpeen, jotta funktio voidaan määritellä otsikkotiedostossa tuottamatta virheilmoitusta, koska funktio on määritelty useammassa kuin yhdessä käännösyksikössä).

(#,##)

# – ja # # – operaattoreita käytetään #define-makron kanssa. Käyttämällä # saa ensimmäisen argumentin#: n jälkeen palautettua merkkijonona lainausmerkeissä. Esimerkiksi komento

#define as_string( s ) # s

saa kääntäjän kääntämään tämän komennon

puts( as_string( Hello World! ) ) ;

muotoon

puts( "Hello World!" );

käyttäen # # – konsatenaatteja siitä, mikä on ennen ## ja mikä sen jälkeen. Esimerkiksi komento

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

tekee kääntäjän käännöksen

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

into

printf( "%d", xy);

, joka tietenkin näyttää 10 vakiotulosteelle.

on mahdollista yhdistää makroargumentti vakion etuliitteellä tai loppuliitteellä, jotta saadaan kelvollinen tunniste kuten

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

, joka määrittää funktion nimeltä my_bar(). Mutta se ei ole mahdollista integroida makro argumentti vakio merkkijono käyttäen concatenation operaattori. Tällaisen efektin aikaansaamiseksi voidaan käyttää ANSI C-ominaisuutta, jonka mukaan kahden tai useamman peräkkäisen merkkijonovakion katsotaan vastaavan kohdattaessa yhtä merkkijonovakiota. Tätä ominaisuutta käyttäen voidaan kirjoittaa

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

, minkä makrosuoritin muuttaa

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

, jonka puolestaan C-jäsennin tulkitsee yhden merkkijonon vakioksi.

seuraavalla konstilla voidaan muuttaa numeerinen vakio merkkijonolukuluvuiksi

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

Tämä on hieman hankalaa, sillä sitä laajennetaan 2-portaisesti. Ensin num2str(CONST) korvataan str(23), joka puolestaan korvataan "23". Tästä voi olla hyötyä seuraavassa esimerkissä:

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

Tämä antaa sinulle mukavan debug-viestin sisältäen tiedoston ja rivin, jolla viesti annettiin. Jos VIRHEENJÄLJITYSTÄ ei ole määritelty, virheenkorjausviesti katoaa kokonaan koodistasi. Ole varovainen, ettet käytä tällaista konstruktiota minkään kanssa, jolla on sivuvaikutuksia, koska tämä voi johtaa virheisiin, jotka näkyvät ja katoavat riippuen kokoamisparametreista.

makroja

makroja ei ole tyyppitarkistettu, joten ne eivät arvioi argumentteja. He eivät myöskään tottele ulottuvuutta kunnolla, vaan yksinkertaisesti ottavat itselleen välitetyn merkkijonon ja korvaavat jokaisen makroargumentin esiintymän makron tekstissä kyseisen parametrin varsinaisella merkkijonolla (koodi kopioidaan kirjaimellisesti paikkaan, josta sitä kutsuttiin).

esimerkki makron käytöstä:

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

— tuloksen ”a” pitäisi olla ”2” (b + c = 16 -> passed to ADD -> 16 / slips -> tulos on ”2”)

Huom:
on yleensä huono käytäntö määritellä makrot otsikoihin.

makro tulisi määritellä vain silloin, kun samaa tulosta ei ole mahdollista saavuttaa funktiolla tai muulla mekanismilla. Jotkut kääntäjät pystyvät optimoimaan koodin, jossa puhelut pieniin toimintoihin korvataan inline-koodilla, mikä estää mahdollisen nopeusedun.Käyttämällä typedefs, enums, ja inline (C99) on usein parempi vaihtoehto.

yksi harvoista tilanteista, joissa inline-funktiot eivät toimi-joten sinun on aika lailla pakko käyttää funktion kaltaisia makroja sen sijaan-on aikavakioiden kääntämisen alustaminen (struktuurien staattinen alustus).Tämä tapahtuu, kun argumentit makrolle ovat literaaleja, jotka kääntäjä voi optimoida toiselle kirjaimelle.

#errorEdit

#virhedirektiivi pysäyttää kokoamisen. Kun sellainen havaitaan, standardi määrittää, että kääntäjän tulee lähettää diagnostiikka, joka sisältää jäljellä olevat poletit direktiivissä. Tätä käytetään enimmäkseen virheenkorjaustarkoituksiin.

ohjelmoijat käyttävät ”#erroria” ehdollisen lohkon sisällä pysäyttääkseen kääntäjän välittömästi, kun ”#if” tai ”#ifdef” — lohkon alussa — havaitsee käännösajan ongelman.Normaalisti kääntäjä ohittaa lohkon (ja” #error ” – direktiivin sen sisällä) ja kokoaminen etenee.

 #error message

#warningEdit

monet laatijat kannattavat #varoitusdirektiiviä. Kun sellainen havaitaan, kääntäjä lähettää vianmäärityksen, joka sisältää jäljellä olevat poletit direktiivissä.

 #warning message

#undefEdit

#undefedit-direktiivi määrittää makron. Tunnisteen tarvetta ei ole aiemmin määritelty.

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

#if-komento tarkistaa, arvioiko kontrolloiva ehdollinen lauseke nollan vai nonzeron, ja sulkee pois tai sisältää vastaavasti koodilohkon. Esimerkiksi:

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

ehdollinen lauseke voi sisältää minkä tahansa C-operaattorin lukuun ottamatta tehtäväoperaattoreita, lisäys-ja säätöoperaattoreita, operaattorin osoitetta ja operaattorin kokoa.

yksi esikäsittelyssä käytetty yksilöivä operaattori eikä missään muualla ole määritelty operaattori. Se palauttaa arvon 1, jos suluissa mahdollisesti oleva makronimi on tällä hetkellä määritelty; 0, jos ei.

#endif-komento päättää #if#ifdef tai #ifndef.

#elif-komento on samankaltainen kuin #if, paitsi että sitä käytetään purkamaan yksi koodilohkojen sarjasta. Esim.:

 #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

#ifdef-komento on samanlainen kuin #if, paitsi että sitä seuraava koodilohko valitaan, jos määritellään makronimi. Tässä suhteessa

#ifdef NAME

vastaa

#if defined NAME

#ifndef-komento on samanlainen kuin #ifdef, paitsi että testi on käänteinen:

#ifndef NAME

vastaa

#if !defined NAME

#LINEEDIT

tätä esiprosessoridirektiiviä käytetään asettamaan tiedoston nimi ja direktiiviä seuraavan rivin rivinumero uusille arvoille. Tätä käytetään__ tiedosto __ja__ rivi _ _ makrot.

Vastaa

Sähköpostiosoitettasi ei julkaista.