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)


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__)

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.


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))

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);



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)

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.