Як зробити варіативний макрос (змінна кількість аргументів)


196

Я хочу написати макрос на C, який приймає будь-яку кількість параметрів, а не конкретне число

приклад:

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

де Xє будь-яка кількість параметрів

Мені це потрібно, тому що whateverперевантажений і його можна викликати з 2 або 4 параметрами.

Я спробував визначити макрос двічі, але друге визначення замінило перше!

Компілятор, з яким я працюю, - це g ++ (точніше, mingw)


8
Ви хочете C або C ++? Якщо ви використовуєте C, чому ви компілюєте компілятор C ++? Для використання правильних варіантів макросів C99 вам слід компілювати компілятор C, який підтримує C99 (наприклад, gcc), а не компілятор C ++, оскільки C ++ не має стандартних варіативних макросів.
Кріс Луц

Ну, я припустив, що C ++ є надзвичайним набором С у цьому плані ..
hasen

tigcc.ticalc.org/doc/cpp.html#SEC13 має детальне пояснення варіативних макросів.
Gnubie

Хороше пояснення та приклад тут: http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq

3
Для майбутніх читачів: C не є найдостішим C ++. Вони діляться багатьма багатьма речами, але є правила, які зупиняють їх підмножини та супернабір один одного.
Фарап

Відповіді:


295

C99 спосіб, також підтримується компілятором VC ++.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)

8
Я не думаю, що C99 вимагає ## перед VA_ARGS . Це може бути просто VC ++.
Кріс Лутц

98
Причиною ## перед VA_ARGS є те, що вона проковтує попередню кому у випадку, якщо список змінних аргументів порожній, наприклад. FOO ("a") розширюється до printf ("a"). Це розширення gcc (і, можливо, vc ++), C99 вимагає, щоб на місці еліпсису був принаймні один аргумент.
jpalecek

110
##не потрібен і не є портативним. #define FOO(...) printf(__VA_ARGS__)робить роботу портативним способом; fmtпараметр може бути опущений з визначення.
alecov

4
IIRC, ## є специфічним для GCC та дозволяє передавати нульові параметри
Мауг каже, що повертається Моніка

10
Синтаксис ## - працює також з llvm / clang та компілятором Visual Studio. Тож це може бути не портативно, але його підтримують основні компілятори.
К. Бірман

37

__VA_ARGS__це стандартний спосіб зробити це. Не використовуйте для компілятора хаки, якщо цього не потрібно.

Мені дуже прикро, що я не можу коментувати оригінальний пост. У будь-якому випадку, C ++ не є сукупністю C. Це справді нерозумно складати свій код C за допомогою компілятора C ++. Не робіть того, що робить Донні.


8
"Справді нерозумно компілювати свій C код за допомогою компілятора C ++" => Не вважається таким всім (включаючи мене). Дивіться, наприклад, основні рекомендації C ++: CPL.1: Віддайте перевагу C ++ до C , CPL.2: Якщо вам потрібно використовувати C, використовуйте загальний підмножина C і C ++ і компілюйте код C як C ++ . Мені важко думати про те, які "C-only-isms" дійсно потрібні, щоб не варто програмувати сумісні підмножини, а комітети C і C ++ наполегливо працювали над тим, щоб цей сумісний підмножина був доступний.
HostileFork каже, що не довіряйте SE

4
@HostileFork Справедливо, хоча, звичайно, люди на C ++ хотіли б заохотити використання C ++. Інші ж не згодні; Linux Торвальдс, наприклад, по - видимому , відкинув множинним запропонував патчі Linux-ядра , які намагаються замінити ідентифікатор classз klassдля можливості здійснення компіляції з компілятором C ++. Також зауважте, що є деякі відмінності, які будуть вас заважати; наприклад, потрійний оператор не оцінюється однаково в обох мовах, а inlineключове слово означає щось зовсім інше (як я дізнався з іншого питання).
Кайл Странд

3
Для дійсно багатопрофільних системних проектів, таких як операційна система, ви дійсно хочете дотримуватися суворого C, тому що компілятори C зустрічаються набагато частіше. У вбудованих системах все ще є платформи без компіляторів C ++. (Є платформи з лише прохідними компіляторами C!) Компілятори C ++ викликають нерви, особливо для кіберфізичних систем, і я б здогадався, що я не єдиний вбудований програміст / програміст C з таким почуттям.
пониження

2
@downbeat Незалежно від того, використовуєте ви C ++ для виробництва чи ні, якщо ви стурбовані суворістю, то можливість компілювати з C ++ дає магічні сили для статичного аналізу. Якщо у вас є запит, який ви хочете створити з кодової бази С ... цікаво, чи використовуються певні типи певними способами, навчитися використовувати type_traits, можна створити для нього цільові інструменти. Що ви платите великі гроші за інструмент статичного аналізу C, який можна зробити, можна зробити за допомогою трохи вмісту C ++ та компілятора, який у вас уже є ...
HostileFork каже, що не довіряйте SE

1
Я говорю до питання Linux. (Я щойно помітив, що на ньому написано "Linux Torvalds" га!)
переможене

28

Я не думаю, що це можливо, ви можете підробити це з подвійними паролями ... так само, поки аргументи вам не потрібні окремо.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))

21
Хоча можливо мати різноманітний макрос, використання подвійних дужок є гарною порадою.
Девід Родрігес - дрибес

2
Компілятор XC від Microchip не підтримує різноманітні макроси, тому цей трюк з подвійними дужками - найкраще, що можна зробити.
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;
}

Якщо компілятор не розуміє варіативні макроси, ви також можете викреслити PRINT за допомогою одного з наступного:

#define PRINT //

або

#define PRINT if(0)print

Перший коментує інструкції PRINT, другий запобігає команді PRINT через NULL, якщо це умова. Якщо встановлено оптимізацію, компілятор повинен викреслити ніколи не виконані інструкції, такі як: if (0) print ("привіт світ"); або ((недійсне) 0);


8
#define PRINT // не замінить PRINT на //
bitc

8
#define PRINT, якщо (0) друк також не є хорошою ідеєю, оскільки код для виклику може мати власне інше - якщо для виклику PRINT. Краще: #define PRINT if (true); else print
bitc

3
Стандартним "нічого не робити, витончено" є do {} while (0)
vonbrand

Належна ifверсія "не робити цього", яка враховує структуру коду, - if (0) { your_code } elseце крапка з комою після завершення розширення макросу else. У whileверсії виглядає наступним чином : while(0) { your_code } Проблема з do..whileверсією є те , що код в do { your_code } while (0)робляться один раз, гарантований. У всіх трьох випадках, якщо your_codeце порожньо, це належне do nothing gracefully.
Джессі Чісгольм

4

пояснено для g ++ тут, хоча він є частиною C99, тому він повинен працювати для всіх

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

швидкий приклад:

#define debug(format, args...) fprintf (stderr, format, args)

3
Варіантні макроси GCC не є макросами варіанта C99. GCC має різноманітні макроси C99, але G ++ не підтримує їх, оскільки C99 не входить до C ++.
Кріс Лутц

1
Насправді g ++ буде компілювати C99 макроси у файлах C ++. Однак воно видасть попередження, якщо його складено з "-педантиком".
Алекс Б

2
Це не C99. С99 використовують макрос VA_ARGS ).
qrdl

1
C ++ 11 також підтримує __VA_ARGS__, хоча вони підтримуються компіляторами і в попередніх версіях, як розширення.
Етуріс

1
Це не працює для printf ("привіт"); там, де немає вар-аргів. Будь-який загальний спосіб виправити це?
БТР Найду
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.