Variadisk makro i C-förprocessorn

Ett variadisk makro är en egenskap hos vissa datorprogrammeringsspråk , särskilt C-förprocessorn , där ett makro kan förklaras acceptera ett varierande antal argument .

Variable-argument macros introducerades 1999 i ISO/IEC 9899:1999 ( C99 ) revideringen av språkstandarden C och 2011 i ISO/IEC 14882:2011 ( C++11 ) revisionen av språkstandarden C++ . Stöd för variatiska makron utan argument lades till i C++20 och kommer att läggas till i C23 .

Deklarationssyntax

Deklarationssyntaxen liknar den för variadiska funktioner : en sekvens med tre punkter " ... " används för att indikera att ett eller flera argument måste skickas. Under makroexpansion ersätts varje förekomst av den speciella identifieraren __VA_ARGS__ i makroersättningslistan av de godkända argumenten.

Dessutom kan vanliga makroargument listas före ... , men vanliga argument kanske inte listas efter ... .

Det finns inga medel för att komma åt enskilda argument i variabelargumentlistan, inte heller för att ta reda på hur många som godkändes. Däremot kan makron skrivas för att räkna antalet argument som har godkänts.

Både C99- och C++11- standarderna kräver minst ett argument, men sedan C++20 har denna begränsning upphävts genom det funktionella makrot __VA_OPT__ . Makrot __VA_OPT__ ersätts av dess argument när argument finns, och utelämnas annars. Vanliga kompilatorer tillåter dock att nollargument skickas före detta tillägg.

C-förprocessorreglerna förhindrar makronamn i argumentet för __VA_OPT__ från att expandera rekursivt. Det är dock möjligt att kringgå denna begränsning upp till ett godtyckligt fast antal rekursiva expansioner.

Stöd

Flera kompilatorer stöder makron med variabelargument vid kompilering av C- och C++-kod: GNU Compiler Collection 3.0, Clang (alla versioner), Visual Studio 2005 , C++Builder 2006 och Oracle Solaris Studio (tidigare Sun Studio) Forte Developer 6-uppdatering 2 (C++ version 5.3). GCC stöder även sådana makron vid kompilering av Objective-C .

Stöd för makrot __VA_OPT__ för att stödja nollargument har lagts till i GNU Compiler Collection 8, Clang 6 och Visual Studio 2019 .

Exempel

Om en printf -liknande funktion dbgprintf() önskades, som skulle ta filen och radnumret som den anropades från som argument, gäller följande lösning.


    
                     
                      
                    






























 // Vår implementerade funktion  void  realdbgprintf  (  const  char  *  SourceFilename  ,  int  SourceLineno  ,  const  char  *  CFormatString  ,  ...);  // På grund av begränsningar av stödet för variadic makro i C++11 kan följande  // enkla lösning misslyckas och bör därför undvikas:  //  // #define dbgprintf(cformat, ...) \  // realdbgprintf (__FILE__, __LINE__, cformat, __VA_ARGS__)  //  // Anledningen är att  //  // dbgprintf("Hallo")  //  // utökas till  //  // realdbgprintf (__FILE__, __LINE__, "Hallo", )  //  // där kommatecknet före den avslutande klammerparentesen kommer att resultera i ett syntaxfel.  //  // GNU C++ stöder en icke-portabel tillägg som löser detta.  //  // #define dbgprintf(cformat, ...) \  // realdbgprintf (__FILE__, __LINE__, cformat, ##__VA_ARGS__)  //  // C++20 stöder så småningom följande syntax.  //  // #define dbgprintf(cformat, ...) \  // realdbgprintf (__FILE__, __LINE__, cformat __VA_OPT__(,) __VA_ARGS__) //  //  Genom att använda 'cformat'-strängen som en del av de variatiska argumenten kan vi  // / kringgå de ovan nämnda oförenligheterna. Det här är knepigt men   // bärbart.  #define dbgprintf(...) realdbgprintf (__FILE__, __LINE__, __VA_ARGS__) 

dbgprintf() kan sedan anropas som

  dbgprintf  (  "Hej världen"  ); 

som expanderar till

    realdbgprintf  (  __FILE__  ,  __LINE__  ,  "Hej världen"  ); 

Ett annat exempel är

    dbgprintf  (  "%d + %d = %d"  ,  2  ,  2  ,  5  ); 

som expanderar till

      realdbgprintf  (  __FILE__  ,  __LINE__  ,  "%d + %d = %d"  ,  2  ,  2  ,  5  ); 

Utan variatiska makron är det inte direkt möjligt att skriva omslag till printf . Standardlösningen är att använda stdargs -funktionaliteten i C/C++ och ha funktionsanropet vprintf istället.

Efterföljande kommatecken

Det finns ett portabilitetsproblem med att generera ett avslutande kommatecken med tomma args för variadiska makron i C99 . Vissa kompilatorer (t.ex. Visual Studio när de inte använder den nya standardkonforma förprocessorn) kommer tyst att eliminera det avslutande kommatecken. Andra kompilatorer (t.ex. GCC) stöder att sätta ## framför __VA_ARGS__ .

# define MYLOG(FormatLiteral, ...) fprintf (stderr, "%s(%u): " FormatLiteral "\n", __FILE__, __LINE__, __VA_ARGS__)

Följande applikation fungerar

  MYLOG  (  "För många ballonger %u" ,  42  )  ; 

som expanderar till

        fprintf  (  stderr  ,  "%s(%u): "  "För många ballonger %u"  "  \n  "  ,  __FILE__  ,  __LINE__  ,  42  ); 

vilket motsvarar

      fprintf  (  stderr  ,  "%s(%u): För många ballonger %u  \n  "  ,  __FILE__  ,  __LINE__  ,  42  ); 

Men titta på den här applikationen:

 MYLOG  (  "Obs!"  ); 

som expanderar till

        fprintf  (  stderr  ,  "%s(%u): "  "Obs!"  "  \n  "  ,  __FILE__  ,  __LINE__  ,  ); 

som genererar ett syntaxfel med GCC.

GCC stöder följande (icke-portabla) tillägg:

# define MYLOG(FormatLiteral, ...) fprintf (stderr, "%s(%u): " FormatLiteral "\n", __FILE__, __LINE__, ##__VA_ARGS__)

vilket tar bort det avslutande kommatecken när __VA_ARGS__ är tom.

C23 löser detta problem genom att introducera __VA_OPT__ som C++.

Alternativ

Innan existensen av variabelargument i C99 var det ganska vanligt att använda dubbelt kapslade parenteser för att utnyttja det variabla antalet argument som kunde levereras till printf()- funktionen :

#define dbgprintf(x) realdbgprintf x

dbgprintf() kan sedan anropas som:

   dbgprintf  ((  "Hej världen %d"  ,  27  )); 

som expanderar till:

   realdbgprintf  (  "Hej världen %d"  ,  27  ); 

Se även