Makrot C ovat tehokas ominaisuus esikäsittelyvaiheessa, jonka avulla ohjelmoijat voivat määritellä vakioita, inline koodin nippetit, ja suorittaa ehdollisen kokoelman. Kun niitä käytetään tehokkaasti, ne parantavat merkittävästi koodin uudelleenkäytettävyyttä ja ylläpidettävyys, jolloin kehittäjät voivat kirjoittaa puhtaampaa, joustavampia ohjelmia. Kuitenkin niiden teho tulee riskejä; epäasianmukainen makron käyttö voi ottaa käyttöön hienovaraisia vikoja ja tehdä koodin vaikeammaksi debug. Tämä artikkeli tutkii, miten valjastaa makros mahdollisimman hyötyä samalla välttäen yhteisiä sudenkuoppia, parhaita käytäntöjä otettu teollisuuden kokemus ja C standardi.

Mitä Makrot ovat?

Makrot määritellään -direktiivillä, ja ne käsitellään C-esiprosessorin toimesta ennen koostamisen aloittamista. Esiprosessori suorittaa yksinkertaisen tekstin korvaamisen: kaikki lähdekoodin makronimen esiintyminen korvataan makroon. Tämä korvaaminen on puhtaasti tekstikohtainen eikä se vastaa soveltamisalaa tai tyyppitarkistusta. Makrot voivat olla []-objektityyppisiä[] (käytetään vakioihin) tai toimintoa muistuttavia[[] (käytetään koodinviipaleisiin, jotka ottavat argumentteja).

#define PI 3.14159 // object-like macro
#define SQUARE(x) ((x)*(x)) // function-like macro

Tämän esikäsittelyvaiheen ymmärtäminen on ratkaisevan tärkeää, koska makrot eivät noudata samoja sääntöjä kuin toiminnot tai muuttujat. Niitä laajennetaan inline, mikä antaa niille suorituskyvyn etuja, mutta tuo myös ainutlaatuisia haasteita.

Makrojen edut koodin uudelleenkäytettävyyden ja kunnossapidon kannalta

Kun makroja käytetään harkitusti, ne tarjoavat useita konkreettisia etuja suurissa tai suorituskykyherkissä koodausperusoissa:

  • Code Uudelleenkäyttö [: Määrittele yhteinen laskenta tai snippet kerran ja käytä sitä koko ohjelman ajan. Makropäivityksen muuttaminen automaattisesti.
  • Taikanumeroiden määrittäminen [: Sen sijaan, että hajautettaisiin kirjaimia kuten tai , annetaan niille mielekkäitä nimiä. Tämä parantaa luettavuutta ja tekee virityksestä merkityksetöntä.
  • Performanssivoitot[]: Funktiomakrot välttävät funktiokutsun yläpäätä. Pienissä, usein käytetyissä toiminnoissa (esim. min/max, neliö) makrot voivat olla nopeampia kuin jopa inline-toiminnot tietyissä kääntäjissä, joissa inliningiä ei ole taattu.
  • ]Koodin kokoaminen[: Makrot toimivat käsi kädessä :n kanssa, :n kanssa ja [:n kanssa, jotta voidaan sisällyttää tai sulkea pois koodi, joka perustuu kokoamisaikaan. Tämä on olennaista alustakohtaisen koodin, vianetsinnän tai ominaisuustogglesin kannalta.
  • Yksinkertaistettu asetus[: Määrittelemällä makrot, joilla on eri arvoja yhdessä otsikossa, voit mukauttaa koko koodikannan käyttäytymistä koskematta täytäntöönpanotiedostoihin.

Kestävä makros: parhaat käytännöt

Makrojen joustavuus on myös niiden vaara. Huonosti kirjoitettu makro voi aiheuttaa odottamatonta käyttäytymistä, jota on vaikea jäljittää. Noudata näitä parhaita käytäntöjä pitääksesi makrosi turvassa ja ylläpidettävissä.

Aina Parentresize Parametrit ja koko lauseke

Koska makrot ovat tekstikorvauksia, operaattorin ensisijainen tavoite voi murtaa ne. Makron kaltainen [ laajenee ] (joka vastaa 11, ei 25) (eli se on koodattava kaikki parametrit sulkuihin ja käärittävä koko lauseke:

#define SQUARE(x) ((x) * (x)) // now SQUARE(3+2) works correctly

Vältä useita arvioita Argumenttien

Makroväittämät korvataan ole-puolella. Jos argumentilla on sivuvaikutuksia (esim. ), se voidaan arvioida useammin kuin kerran.

#define MAX(a,b) ((a) > (b) ? (a) : (b))
int x = 5;
int max = MAX(x++, 10); // expands to ((x++) > (10) ? (x++) : (10)) // x incremented twice!

Tämän välttämiseksi suosia inline toimintoja tai käyttää huolellista nimeäminen käytäntöjä varoittaa käyttäjiä. Jos sinun täytyy käyttää makro, dokumentti, että argumentit ei pitäisi sisältää sivuvaikutuksia.

Käytä isoja makronimiä

Makrot on kirjoitettu isoin kirjaimin (esim. , ). Näin ne erottuvat muuttujista ja toiminnoista, mikä vähentää onnettomuusvaaraa.

Päätä makrot Do-While moniosaisten lohkojen kanssa

Määritellessään makroa, joka sisältää useita lauseita, kääri ne -silmukkaan. Näin varmistetaan, että makro käyttäytyy oikein kaikissa yhteyksissä (esim. :n jälkeen ilman sulkuja):

#define LOG_ERROR(msg) do { \
 fprintf(stderr, "Error: %s\n", msg); \
 exit(1); \
} while (0)

Asiakirja Makrot

Makroilla ei ole toiminnallisuuden itse dokumentoivaa luonnetta. Lisää aina kommentteja, joissa kuvataan argumentteja, odotettua käyttäytymistä ja rajoituksia. Käytä tarvittaessa D-happi-tyylisiä kommentteja.

Edistyneet makrotekniikat

C-esiprosessori tarjoaa tehokkaita operaattoreita, jotka menevät yksinkertaisen korvaamisen yli. Näiden hallinta voi tehdä makroista vieläkin joustavampia.

Merkkijono

-operaattori muuttaa makroparametrin merkkijonoksi kirjaimellisesti. Tämä on hyödyllinen vianetsintä- tai kirjautumisvirheiden varalta:

#define PRINT_VAR(x) printf(#x " = %d\n", x)
int value = 42;
PRINT_VAR(value); // prints: value = 42

Token Pasting with

-operaattori yhdistää kaksi kuponkia yhteen kuponkiin. Näin voit luoda tunnisteita dynaamisesti käännettäessä:

#define CREATE_VAR(prefix, index) prefix ## index
int CREATE_VAR(arr, 0) = 10; // becomes int arr0 = 10;

Token pasteing käytetään laajalti X makros ja luoda toistuvia koodikuvioita.

Variadic Macros (C99)

C99:ssä käyttöön otetut variadic macrot hyväksyvät vaihtelevan määrän argumentteja käyttäen . Tämä mahdollistaa joustavan kirjautumisen ja vianetsintä makrot:

#define DEBUG_PRINT(fmt, ...) \
 fprintf(stderr, "DEBUG: " fmt, __VA_ARGS__)
DEBUG_PRINT("x = %d, y = %d", x, y);

C11:ssä ja myöhemmin voit käyttää :a käsitelläksesi jäljitysviittoja tyhjin perustein, mikä parantaa siirrettävyyttä.

Yhteinen pitfalls ja miten välttää niitä

Jopa kokeneet C-kehittäjät kohtaavat makroon liittyviä vikoja. Näiden sudenkuoppien tunnistaminen varhaisessa vaiheessa estää päänsärkyä.

  • ]Kaipaavat puolipisteet tai raudoitukset[]: Makro, joka laajenee lohkoon ilman jälkiä puolipiste voi aiheuttaa syntaksivirheitä. ] kuvio ratkaisee tämän.
  • Käyttäjän etuoikeus yllätyksiä [: Aina sulkukokoon . Kuten edellä todettiin.
  • Varjostus makrot toimintoja[: Jos määrittelet makron, jolla on sama nimi kuin normaalilla kirjastotoiminnolla, se voi hiljaa ohittaa sen. Käytä ainutlaatuista nimeä tai #undef.
  • Ei-konstantinen instantizers[]: Object-like makros käytetään vakioina, mutta niitä ei ole kirjoitettu. Niitä voidaan käyttää vakioilmauksissa vain, jos laajennus itsessään on vakioilmaus.
  • ] Vianetsintävaikeus[: Makrot laajennetaan ennen kuin kääntäjä näkee ne, joten virheviestit viittaavat laajennettuun koodiin, eivät makronimeen. -lipun käyttäminen GCC:n tai Clang:n kanssa (esim. ) osoittaa esikäsitellyn tuotoksen jäljittämiseksi.

Vaihtoehtoja makros: const, enum, ja Inline toiminnot

Nykyaikainen C tarjoaa turvallisempia vaihtoehtoja, jotka usein poistavat makroen tarpeen:

  • [] muuttujat[]: Numeerinen vakio on parempi . Niillä on asianmukainen tyyppitarkastus ja kunnioitus laajuus. Ne eivät kuitenkaan ole kaikissa yhteyksissä (esim., matriisikoko C90). C99 ja myöhemmin voidaan käyttää matriisikokoihin, jos muuttuja on todella vakio (esim. ).
  • [[]: Aiheeseen liittyvien kokonaislukuvakioiden osalta enums on tyyppiturvallinen ja automaattisesti määrittää arvot. Niitä käytetään laajalti virhekoodeihin ja lippuihin.
  • ]Linjatoiminnot[: Esitetty C99:ssä, toiminnot tarjoavat makron suorituskyvyn täysin tyyppiturvallisella, vianetsintätuella ja sivuvaikutusten välttämisellä. Esimerkiksi:
static inline int square(int x) {
 return x * x;
}

Käytä inline-toimintoja aina, kun tarvitset toimintomaisen makron, joka arvioi argumentteja täsmälleen kerran. Ainoa jäljellä oleva käyttötapaus toimintomakroille on, kun sinun on toimittava tyypeillä (esim. GNU C:n tyyppi) tai tarvitset kykyä avata uusi soveltamisala (kuten työsi aikana idioomi).

Real-World Esimerkkejä: Vianetsintä ja asetukset

Makrot ovat erinomaisia ehdollisissa skenaarioissa. Tässä on kaksi käytännön sovellusta:

Vianetsintäloggaus, jossa on kommentin aika-ohjaus

#ifdef DEBUG
 #define LOG(fmt, ...) fprintf(stderr, "[DEBUG] " fmt, ##__VA_ARGS__)
#else
 #define LOG(fmt, ...) /* no-op */
#endif

Tämä makrohirsi on -muodossa, ja tyhjä makro aiheuttaa nollan yläpuolella julkaisun rakentaa. ennen on GNU laajennus, joka poistaa jäljitys pilkun, kun ei ole esitetty väitteitä (tuki myös C20 kanssa ).

Laitoskohtainen koodi

#if defined(_WIN32) || defined(_WIN64)
 #define PLATFORM "Windows"
#elif defined(__linux__)
 #define PLATFORM "Linux"
#else
 #define PLATFORM "Unknown"
#endif

Tämä malli eristää alustan riippuvuudet ilman päällekkäisiä kokonaisia toimintoja. Yhdistettynä vartijoihin, se pitää otsikot puhtaina ja ylläpidettävissä.

Päätelmät

Makrot ovat edelleen välttämätön työkalu C-koodin uudelleenkäytettävyydelle ja ylläpidolle, erityisesti suorituskriittisissä tai sulautetuissa olosuhteissa, joissa jokainen sykli on tärkeä. Kun niitä käytetään oikein oikeilla sulkujen kanssa, rajalliset sivuvaikutukset ja asianmukainen nimeäminen.Ne vähentävät kuitenkin päällekkäisyyttä ja keskittävät konfiguraatiota. Nykyajan C-mallissa on kuitenkin trendi korvata toimintotyyppiset makrot inline-toiminnoilla tai geneerisillä (käyttäen [] C11) tyypin turvallisuuden ja vianetsintäkyvyn saavuttamiseksi. Esineen kaltaisille makroille ja [ ovat usein ylivoimaisia.

Kun tasapainottaa perinteistä makrotehoa näillä turvallisemmilla vaihtoehdoilla, voit kirjoittaa C-koodin, joka on sekä tehokas että vankka. Lisätietoja saat []GNU C -esiprosessorikäsikirjasta ja makroja koskevasta C-viittauksesta [. Makrosoppien ymmärtäminen on myös yksityiskohtaisesti mainittu tässä -embedded.com-artikkelissa[.