Cara membuat makro variadik (jumlah variabel argumen)

196

Saya ingin menulis makro di C yang menerima sejumlah parameter, bukan angka tertentu

contoh:

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

di mana Xsejumlah parameter

Saya perlu ini karena whateverkelebihan beban dan dapat dipanggil dengan 2 atau 4 parameter.

Saya mencoba mendefinisikan makro dua kali, tetapi definisi kedua menimpa yang pertama!

Kompiler yang saya gunakan adalah g ++ (lebih khusus, mingw)

Hasen
sumber
8
Anda ingin C atau C ++? Jika Anda menggunakan C, mengapa Anda mengkompilasi dengan kompiler C ++? Untuk menggunakan macro variadic C99 yang tepat, Anda harus mengkompilasi dengan kompiler C yang mendukung C99 (seperti gcc), bukan kompiler C ++, karena C ++ tidak memiliki macro variadic standar.
Chris Lutz
Nah, saya berasumsi C ++ adalah satu set super C dalam hal ini ..
Hasen
tigcc.ticalc.org/doc/cpp.html#SEC13 memiliki penjelasan terperinci tentang makro variadic.
Gnubie
Penjelasan dan contoh yang baik ada di sini http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq
3
Untuk pembaca masa depan: C bukan subest dari C ++. Mereka berbagi banyak hal, tetapi ada aturan yang menghentikan mereka menjadi bagian dan superset satu sama lain.
Pharap

Jawaban:

295

Cara C99, juga didukung oleh VC ++ compiler.

#define FOO(fmt, ...) printf(fmt, ##__VA_ARGS__)
Alex B
sumber
8
Saya tidak berpikir C99 membutuhkan ## sebelum VA_ARGS . Itu mungkin saja VC ++.
Chris Lutz
97
Alasan ## sebelum VA_ARGS adalah karena ia menelan koma sebelumnya jika daftar variabel-argumen kosong, misalnya. FOO ("a") memperluas ke printf ("a"). Ini adalah ekstensi dari gcc (dan vc ++, mungkin), C99 membutuhkan setidaknya satu argumen untuk menggantikan ellipsis.
jpalecek
108
##tidak diperlukan dan tidak portabel. #define FOO(...) printf(__VA_ARGS__)melakukan pekerjaan dengan cara portabel; yang fmtparameter dapat dihilangkan dari definisi.
alecov
4
IIRC, ## adalah khusus GCC dan memungkinkan melewati parameter nol
Mawg mengatakan mengembalikan Monica
10
Sintaks ## - juga berfungsi dengan llvm / clang dan kompiler Visual Studio. Jadi mungkin tidak portabel, tetapi didukung oleh kompiler utama.
K. Biermann
37

__VA_ARGS__adalah cara standar untuk melakukannya. Jangan gunakan hack khusus-kompiler jika Anda tidak perlu.

Saya benar-benar kesal karena saya tidak dapat mengomentari posting asli. Bagaimanapun, C ++ bukan superset dari C. Ini benar-benar konyol untuk mengkompilasi kode C Anda dengan kompiler C ++. Jangan lakukan apa yang Donny tidak lakukan.

cmccabe
sumber
8
"Sungguh konyol mengkompilasi kode C Anda dengan kompiler C ++" => Tidak dipertimbangkan oleh semua orang (termasuk saya). Lihat misalnya pedoman inti C ++: CPL.1: Lebih suka C ++ ke C , CPL.2: Jika Anda harus menggunakan C, gunakan subset umum C dan C ++, dan kompilasi kode C sebagai C ++ . Saya sulit sekali memikirkan "C-only-isme" apa yang benar-benar dibutuhkan untuk membuatnya tidak diprogram dalam subset yang kompatibel, dan komite C dan C ++ telah bekerja keras untuk membuat subset yang kompatibel tersedia.
HostileFork berkata jangan percaya
4
@HostileFork Cukup adil, meskipun tentu saja orang -orang C ++ ingin mendorong penggunaan C ++. Namun, yang lain tidak setuju; Linux Torvalds, misalnya, tampaknya menolak beberapa tambalan kernel Linux yang diusulkan yang mencoba untuk mengganti pengidentifikasi classdengan klassmengizinkan kompilasi dengan kompiler C ++. Perhatikan juga bahwa ada beberapa perbedaan yang akan membuat Anda marah; misalnya, operator ternary tidak dievaluasi dengan cara yang sama dalam kedua bahasa, dan inlinekata kunci berarti sesuatu yang sama sekali berbeda (seperti yang saya pelajari dari pertanyaan yang berbeda).
Kyle Strand
3
Untuk proyek sistem lintas platform yang benar-benar seperti sistem operasi, Anda benar-benar ingin mematuhi C yang ketat, karena kompiler C jauh lebih umum. Di embedded system, masih ada platform tanpa kompiler C ++. (Ada platform dengan hanya kompiler C yang bisa dilewati!) Kompiler C ++ membuat saya gugup, terutama untuk sistem cyber-fisik, dan saya kira saya bukan satu-satunya programmer perangkat lunak / C tertanam dengan perasaan itu.
suram
2
@downbeat Apakah Anda menggunakan C ++ untuk produksi atau tidak, jika Anda sangat khawatir, maka dapat dikompilasi dengan C ++ memberi Anda kekuatan ajaib untuk analisis statis. Jika Anda memiliki kueri yang ingin Anda buat dari basis kode C ... bertanya-tanya apakah jenis tertentu digunakan dengan cara tertentu, mempelajari cara menggunakan type_traits dapat membuat alat yang ditargetkan untuknya. Apa yang akan Anda bayar banyak untuk alat analisis statis C untuk dilakukan dapat dilakukan dengan sedikit C ++ tahu bagaimana dan kompiler yang sudah Anda miliki ...
HostileFork mengatakan jangan percaya SE
1
Saya berbicara dengan pertanyaan tentang Linux. (Saya hanya memperhatikan bahwa dikatakan "Linux Torvalds" ha!)
suram
28

Saya tidak berpikir itu mungkin, Anda bisa berpura-pura dengan double parens ... asalkan Anda tidak memerlukan argumen secara individual.

#define macro(ARGS) some_complicated (whatever ARGS)
// ...
macro((a,b,c))
macro((d,e))
bengkak
sumber
21
Meskipun dimungkinkan untuk memiliki makro variadik, menggunakan tanda kurung ganda adalah saran yang bagus.
David Rodríguez - dribeas
2
Kompilator XC oleh Microchip tidak mendukung macro variadic, jadi trik kurung ganda ini adalah yang terbaik yang dapat Anda lakukan.
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;
}

Jika kompiler tidak memahami makro variadic, Anda juga dapat menghapus PRINT dengan salah satu dari berikut ini:

#define PRINT //

atau

#define PRINT if(0)print

Komentar pertama mengeluarkan instruksi PRINT, yang kedua mencegah instruksi PRINT karena kondisi NULL if. Jika optimisasi diatur, kompiler harus menghapus instruksi yang tidak pernah dijalankan seperti: if (0) print ("hello world"); atau ((tidak berlaku) 0);

jpalecek
sumber
8
#define PRINT // tidak akan mengganti PRINT dengan //
bitc
8
#define PRINT if (0) print bukan ide yang baik juga karena kode panggilan mungkin memiliki yang lain-jika untuk memanggil PRINT. Lebih baik adalah: #define PRINT if (true);
selain itu
3
Standar "tidak melakukan apa pun, dengan anggun" adalah melakukan {} sementara (0)
vonbrand
Versi yang tepat ifdari "jangan lakukan ini" yang memperhitungkan struktur kode adalah: if (0) { your_code } elsetitik koma setelah ekspansi makro Anda mengakhiri else. The whileVersi terlihat seperti: while(0) { your_code } Masalah dengan do..whileversi adalah bahwa kode dalam do { your_code } while (0)dilakukan sekali, dijamin. Dalam ketiga kasus, jika your_codekosong, itu layak do nothing gracefully.
Jesse Chisholm
4

menjelaskan untuk g ++ di sini, meskipun itu adalah bagian dari C99 jadi harus bekerja untuk semua orang

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

contoh cepat:

#define debug(format, args...) fprintf (stderr, format, args)
DarW
sumber
3
Makro variadic GCC bukan makro variadic C99. GCC memiliki makro variadic C99, tetapi G ++ tidak mendukungnya, karena C99 bukan bagian dari C ++.
Chris Lutz
1
Sebenarnya g ++ akan mengkompilasi macro C99 dalam file C ++. Akan ada peringatan, jika dikompilasi dengan '-pedantic'.
Alex B
2
Itu bukan C99. C99 menggunakan VA_ARGS makro).
qrdl
1
C ++ 11 juga mendukung __VA_ARGS__, meskipun mereka didukung oleh kompiler di versi sebelumnya juga, sebagai ekstensi.
Ethouris
1
Ini gagal berfungsi untuk printf ("hai"); di mana tidak ada var args. Adakah cara umum untuk memperbaikinya?
BTR Naidu