วิธีสร้างมาโครแบบผันแปร (จำนวนอาร์กิวเมนต์ที่ผันแปร)


196

ฉันต้องการเขียนแมโครใน C ที่ยอมรับพารามิเตอร์จำนวนเท่าใดก็ได้ไม่ใช่ตัวเลขที่เฉพาะเจาะจง

ตัวอย่าง:

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

โดยที่Xจำนวนพารามิเตอร์ใด ๆ

ฉันต้องการสิ่งนี้เพราะwhateverโอเวอร์โหลดและสามารถเรียกได้ด้วยพารามิเตอร์ 2 หรือ 4

ฉันพยายามกำหนดมาโครสองครั้ง แต่คำจำกัดความที่สองเขียนทับอันแรก!

คอมไพเลอร์ที่ฉันทำงานด้วยคือ g ++ (โดยเฉพาะเจาะจงมากขึ้น mingw)


8
คุณต้องการ C หรือ C ++ หรือไม่ หากคุณใช้ C ทำไมคุณถึงคอมไพล์คอมไพเลอร์ด้วย C ++ ในการใช้มาโคร C9 แบบแปรผันที่เหมาะสมคุณควรจะคอมไพล์ด้วย C คอมไพเลอร์ที่รองรับ C99 (เช่น gcc) ไม่ใช่คอมไพเลอร์ C ++ เนื่องจาก C ++ ไม่มีมาโครแบบแปรผันมาตรฐาน
คริสลัทซ์

ดีฉันสันนิษฐาน C ++ เป็นชุดสุดของ C ในเรื่องนี้ ..
Hasen

tigcc.ticalc.org/doc/cpp.html#SEC13มีคำอธิบายโดยละเอียดเกี่ยวกับมาโคร variadic
Gnubie

คำอธิบายและตัวอย่างที่ดีอยู่ที่นี่http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
zafarulq

3
สำหรับผู้อ่านในอนาคต: C ไม่ใช่ส่วนย่อยของ C ++ พวกเขาแบ่งปันหลายสิ่งหลายอย่าง แต่มีกฎที่หยุดพวกเขาเป็นส่วนย่อยและ superset ซึ่งกันและกัน
Pharap

คำตอบ:


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 ที่เฉพาะเจาะจงและอนุญาตให้ผ่านพารามิเตอร์ที่เป็นศูนย์
Mawg กล่าวว่าการคืนสถานะโมนิก้า

10
## - ไวยากรณ์ยังทำงานร่วมกับ llvm / clang และ Visual Studio คอมไพเลอร์ ดังนั้นมันอาจจะไม่สามารถพกพาได้ แต่มันก็รองรับโดยคอมไพเลอร์รายใหญ่
K. Biermann

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 Torvalds ได้ปฏิเสธหลายแพทช์ Linux-kernel ที่เสนอที่พยายามแทนที่ตัวระบุclassด้วยklassเพื่ออนุญาตให้คอมไพล์ด้วยคอมไพเลอร์ C ++ นอกจากนี้โปรดทราบว่ามีความแตกต่างบางอย่างที่จะทำให้คุณผิดหวัง ตัวอย่างเช่นผู้ประกอบการที่ประกอบไปด้วยสามไม่ได้รับการประเมินในลักษณะเดียวกันในทั้งสองภาษาและinlineคำหลักหมายถึงสิ่งที่แตกต่างอย่างสิ้นเชิง (เมื่อฉันเรียนรู้จากคำถามที่แตกต่างกัน)
Kyle Strand

3
สำหรับโครงการระบบข้ามแพลตฟอร์มจริง ๆ เช่นระบบปฏิบัติการคุณต้องการยึดมั่นกับ C อย่างเข้มงวดเพราะคอมไพเลอร์ C นั้นเป็นเรื่องธรรมดามาก ในระบบฝังตัวยังคงมีแพลตฟอร์มที่ไม่มีคอมไพเลอร์ C ++ (มีแพลตฟอร์มที่มีคอมไพเลอร์ C เท่านั้นที่ผ่านได้!) คอมไพเลอร์ C ++ ทำให้ฉันรู้สึกกังวลโดยเฉพาะอย่างยิ่งสำหรับระบบทางกายภาพทางไซเบอร์และฉันคิดว่าฉันไม่ใช่ซอฟต์แวร์ฝังตัว / โปรแกรมเมอร์ C เพียงตัวเดียวที่มีความรู้สึก
downbeat

2
@downbeat ไม่ว่าคุณจะใช้ C ++ สำหรับการผลิตหรือไม่ถ้ามันเข้มงวดมากขึ้นคุณก็สามารถคอมไพล์ด้วย C ++ เพื่อให้คุณมีพลังเวทย์ในการวิเคราะห์แบบคงที่ หากคุณมีคำถามที่คุณต้องการสร้างจาก codebase ของ C ... สงสัยว่าถ้ามีบางประเภทที่ใช้วิธีการบางอย่างเรียนรู้วิธีการใช้type_traitsสามารถสร้างเครื่องมือเป้าหมายสำหรับมัน สิ่งที่คุณจะจ่าย bucks ใหญ่สำหรับเครื่องมือการวิเคราะห์แบบคงที่ของ C สามารถทำได้ด้วยบิตของ C ++ รู้วิธีและคอมไพเลอร์ที่คุณมีอยู่แล้ว ...
HostileFork พูดว่าไม่ไว้วางใจ SE

1
ฉันกำลังพูดถึงคำถามของ Linux (ฉันเพิ่งสังเกตเห็นว่ามีคำว่า "Linux Torvalds" ฮ่า!)
downbeat

28

ฉันไม่คิดว่าเป็นไปได้คุณสามารถปลอมมันด้วย parens สองเท่า ... ตราบใดที่คุณไม่จำเป็นต้องมีการโต้แย้งเป็นรายบุคคล

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

21
ในขณะที่มีความเป็นไปได้ที่จะมีมาโครแบบแปรผันได้การใช้วงเล็บคู่เป็นคำแนะนำที่ดี
David Rodríguez - dribeas

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 ("hello world") หรือ ((เป็นโมฆะ) 0);


8
#define PRINT // จะไม่แทนที่ PRINT ด้วย //
bitc

8
#define PRINT หากการพิมพ์ (0) ไม่ใช่ความคิดที่ดีเนื่องจากรหัสการโทรอาจมีรูปแบบของตัวเอง - หากใช้สำหรับเรียกใช้ PRINT ดีกว่าคือ: #define PRINT if (true); print อื่น
bitc

3
มาตรฐาน "ไม่ทำอะไรเลยอย่างสง่างาม" คือทำ {} ในขณะที่ (0)
vonbrand

เหมาะสมifรุ่น "ไม่ทำเช่นนี้" ที่ใช้โครงสร้างรหัสเข้าบัญชีคือลำไส้ใหญ่กึ่งหลังจากที่ขยายตัวแมโครของคุณยุติif (0) { your_code } else รุ่นดูเหมือนว่า: ปัญหากับรุ่นคือรหัสในการจะทำครั้งเดียวรับประกัน ในทั้งสามกรณีถ้าเป็นที่ว่างเปล่ามันเป็นที่เหมาะสม elsewhilewhile(0) { your_code }do..whiledo { your_code } while (0)your_codedo nothing gracefully
Jesse Chisholm

4

อธิบายสำหรับ g ++ ที่นี่ถึงแม้ว่ามันจะเป็นส่วนหนึ่งของ C99 ดังนั้นควรทำงานให้กับทุกคน

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

ตัวอย่างรวดเร็ว:

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

3
มาโคร variadic ของ GCC ไม่ใช่มาโคร variadic C99 GCC มีมาโครแบบผันแปร C99 แต่ G ++ ไม่รองรับเนื่องจาก C99 ไม่ได้เป็นส่วนหนึ่งของ C ++
คริสลัทซ์

1
ที่จริง g ++ จะรวบรวมมาโคร C99 ในไฟล์ C ++ มันจะออกคำเตือนอย่างไรก็ตามถ้าคอมไพล์ด้วย '-pedantic'
Alex B

2
ไม่ใช่ C99 C99 ใช้แมโครVA_ARGS )
qrdl

1
C ++ 11 รองรับ__VA_ARGS__เช่นกันแม้ว่าคอมไพเลอร์เหล่านี้จะรองรับในเวอร์ชันก่อนหน้ารวมถึงส่วนขยาย
Ethouris

1
สิ่งนี้ไม่สามารถทำงานกับ printf ("hi"); เมื่อไม่มี var args มีวิธีทั่วไปในการแก้ไขปัญหานี้อย่างไร
BTR Naidu
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.