Jak zrobić makro variadic (zmienna liczba argumentów)

196

Chcę napisać makro w C, które akceptuje dowolną liczbę parametrów, a nie określoną liczbę

przykład:

#define macro( X )  something_complicated( whatever( X ) )

gdzie Xjest dowolna liczba parametrów

Potrzebuję tego, ponieważ whateverjest przeciążony i można go wywołać z 2 lub 4 parametrami.

Próbowałem zdefiniować makro dwa razy, ale druga definicja zastąpiła pierwszą!

Kompilator, z którym pracuję to g ++ (a dokładniej mingw)

hasen
źródło
8
Czy chcesz C lub C ++? Jeśli używasz C, dlaczego kompilujesz za pomocą kompilatora C ++? Aby korzystać z odpowiednich makr variadic C99, należy kompilować przy użyciu kompilatora C obsługującego C99 (jak gcc), a nie kompilatora C ++, ponieważ C ++ nie ma standardowych makr variadic.
Chris Lutz
Cóż, założyłem, że C ++ jest pod tym względem super zestawem C ..
hasen
tigcc.ticalc.org/doc/cpp.html#SEC13 zawiera szczegółowe wyjaśnienie różnych makr.
Gnubie,
Dobre wyjaśnienie i przykład można znaleźć tutaj http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq
3
Dla przyszłych czytelników: C nie jest subestem C ++. Dzielą wiele rzeczy, ale istnieją reguły, które uniemożliwiają ich podzbiór i wzajemną kontrolę.
Pharap

Odpowiedzi:

295

Sposób C99, obsługiwany również przez kompilator VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)
Alex B.
źródło
8
Nie sądzę, że C99 wymaga ## przed VA_ARGS . To może być po prostu VC ++.
Chris Lutz
98
Powodem ## przed VA_ARGS jest to, że połyka poprzedzający przecinek w przypadku, gdy lista argumentów zmiennych jest pusta, np. FOO („a”) rozwija się do printf („a”). Jest to rozszerzenie gcc (i być może vc ++), C99 wymaga co najmniej jednego argumentu zamiast elipsy.
jpalecek
110
##nie jest potrzebne i nie jest przenośne. #define FOO(...) printf(__VA_ARGS__)wykonuje pracę w przenośny sposób; fmtparametr może być pominięte z definicji.
alecov
4
IIRC, ## jest specyficzny dla GCC i pozwala na przekazanie zerowych parametrów
Mawg mówi przywrócenie Moniki
10
Składnia ## działa również z llvm / clang i kompilatorem Visual Studio. Może więc nie być przenośny, ale jest obsługiwany przez główne kompilatory.
K. Biermann,
37

__VA_ARGS__to standardowy sposób na zrobienie tego. Nie używaj hacków specyficznych dla kompilatora, jeśli nie musisz.

Jestem naprawdę zirytowany, że nie mogę komentować oryginalnego postu. W każdym razie C ++ nie jest nadzbiorem C. Kompilowanie kodu C przy pomocy kompilatora C ++ jest naprawdę głupie. Nie rób tego, co robi Donny Don't.

cmccabe
źródło
8
„Naprawdę głupie jest kompilowanie kodu C za pomocą kompilatora C ++” => Nie jest to uważane przez wszystkich (łącznie ze mną). Zobacz na przykład podstawowe wytyczne dla C ++: CPL.1: Preferuj C ++ od C , CPL.2: Jeśli musisz użyć C, użyj wspólnego podzbioru C i C ++ i skompiluj kod C jako C ++ . Trudno mi myśleć o tym, jakie „tylko C-ismy” naprawdę są potrzebne, aby warto było nie programować w kompatybilnym podzbiorze, a komitety C i C ++ ciężko pracowały nad udostępnieniem tego kompatybilnego podzbioru.
HostileFork mówi: nie ufaj SE
4
@HostileFork Wystarczająco uczciwy, choć oczywiście ludzie z C ++ chcieliby zachęcić do korzystania z C ++. Inni się jednak nie zgadzają; Linux Torvalds, na przykład, najwyraźniej odrzucone stwardnienie zaproponował poprawki jądra Linuksa, które próbują zastąpić identyfikator classz klasscelu umożliwienia kompilowania z C ++. Pamiętaj również, że istnieją pewne różnice, które Cię potkną; na przykład operator trójskładnikowy nie jest oceniany w ten sam sposób w obu językach, a inlinesłowo kluczowe oznacza coś zupełnie innego (jak dowiedziałem się z innego pytania).
Kyle Strand
3
W projektach systemów wieloplatformowych, takich jak system operacyjny, naprawdę chcesz przestrzegać ścisłego C, ponieważ kompilatory C są o wiele bardziej powszechne. W systemach wbudowanych nadal istnieją platformy bez kompilatorów C ++. (Istnieją platformy tylko z możliwymi do przejedzenia kompilatorami C!) Kompilatory C ++ denerwują mnie, szczególnie w przypadku systemów cyberfizycznych, i sądzę, że nie jestem jedynym wbudowanym programistą / programistą C z tym uczuciem.
zniżka
2
@downbeat Bez względu na to, czy używasz C ++ do produkcji, czy nie, jeśli obawiasz się rygorystyczności, to możliwość kompilacji z C ++ daje magiczne moce do analizy statycznej. Jeśli masz zapytanie, które chcesz utworzyć w bazie kodu C ... zastanawiasz się, czy niektóre typy są używane w określony sposób, ucząc się, jak korzystać z type_traits, możesz zbudować dla niego ukierunkowane narzędzia. To, co zapłacisz, za narzędzie do analizy statycznej języka C, można zrobić przy odrobinie znajomości C ++ i kompilatora, który już masz ...
HostileFork mówi: nie ufaj
1
Mówię o kwestii Linuksa. (Właśnie zauważyłem, że jest napisane „Linux Torvalds” ha!)
optymistyczne
28

Nie sądzę, żeby to było możliwe, można to sfałszować podwójnymi parenami ... tak długo, jak nie potrzebujesz pojedynczych argumentów.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))
zirytowany
źródło
21
Chociaż można mieć makro variadic, dobrym pomysłem jest użycie podwójnego nawiasu.
David Rodríguez - dribeas
2
Kompilator XC firmy Microchip nie obsługuje różnych makr, więc ta sztuczka z podwójnym nawiasiem jest najlepsza, co możesz zrobić.
gbmhunter
10
#define DEBUG

#ifdef DEBUG
  #define PRINT print
#else
  #define PRINT(...) ((void)0) //strip out PRINT instructions from code
#endif 

void print(const char *fmt, ...) {

    va_list args;
    va_start(args, fmt);
    vsprintf(str, fmt, args);
        va_end(args);

        printf("%s\n", str);

}

int main() {
   PRINT("[%s %d, %d] Hello World", "March", 26, 2009);
   return 0;
}

Jeśli kompilator nie rozumie różnych makr, możesz także usunąć PRINT za pomocą jednego z poniższych:

#define PRINT //

lub

#define PRINT if(0)print

Pierwszy komentuje instrukcje PRINT, drugi zapobiega instrukcji PRINT z powodu warunku NULL if. Jeśli ustawiono optymalizację, kompilator powinien usuwać nigdy niewykonywane instrukcje, takie jak: if (0) print („hello world”); lub ((nieważny) 0);

jpalecek
źródło
8
#
zdefiniować
8
# zdefiniuj DRUKUJ, jeśli (0) print nie jest dobrym pomysłem, ponieważ kod wywołujący może mieć swój własny kod else-if do wywoływania PRINT. Lepiej jest: #
zdefiniować
3
Standardowe „nic nie rób z gracją” to zrobić {}, podczas gdy (0)
vonbrand,
Prawidłowa ifwersja „nie rób tego”, która uwzględnia strukturę kodu, to: if (0) { your_code } elseśrednik po zakończeniu rozwijania makra else. Do whilewygląda jak wersja: while(0) { your_code } Problem z do..whilewersji jest to, że kod do { your_code } while (0)jest wykonywana raz, gwarantowane. We wszystkich trzech przypadkach, jeśli your_codejest pusty, jest to właściwe do nothing gracefully.
Jesse Chisholm,
4

wyjaśniono tutaj dla g ++, chociaż jest to część C99, więc powinno działać dla wszystkich

http://www.delorie.com/gnu/docs/gcc/gcc_44.html

szybki przykład:

#define debug(format, args...) fprintf (stderr, format, args)
DarenW
źródło
3
Makr variadic GCC nie są makr variadic C99. GCC ma makra variadic C99, ale G ++ ich nie obsługuje, ponieważ C99 nie jest częścią C ++.
Chris Lutz
1
Właściwie g ++ skompiluje makra C99 w plikach C ++. Wyda jednak ostrzeżenie, jeśli zostanie skompilowane z „-pedantic”.
Alex B
2
To nie jest C99. C99 użyj makra VA_ARGS ).
qrdl
1
C ++ 11 obsługuje również __VA_ARGS__, chociaż są one obsługiwane przez kompilatory również we wcześniejszych wersjach, jako rozszerzenie.
Ethouris,
1
To nie działa dla printf („cześć”); gdzie nie ma żadnych argumentów. Jakikolwiek ogólny sposób to naprawić?
BTR Naidu