การสร้างมาโคร C ด้วย ## และ __LINE__ (การเชื่อมต่อโทเค็นกับมาโครการกำหนดตำแหน่ง)


107

ฉันต้องการสร้างมาโคร C ที่สร้างฟังก์ชันด้วยชื่อตามหมายเลขบรรทัด ฉันคิดว่าฉันสามารถทำอะไรบางอย่างได้ (ฟังก์ชันจริงจะมีข้อความอยู่ในเครื่องหมายวงเล็บ):

#define UNIQUE static void Unique_##__LINE__(void) {}

ซึ่งฉันหวังว่าจะขยายไปสู่สิ่งที่ชอบ:

static void Unique_23(void) {}

ไม่ได้ผล ด้วยการต่อโทเค็นมาโครการกำหนดตำแหน่งจะได้รับการปฏิบัติตามตัวอักษรโดยขยายเป็น:

static void Unique___LINE__(void) {}

เป็นไปได้ไหมที่จะทำ?

(ใช่มีเหตุผลจริงๆที่ฉันอยากทำไม่ว่ามันจะดูไร้ประโยชน์แค่ไหนก็ตาม)


ฉันคิดว่าคุณจะได้รับการทำงานที่มีการขยายตัวแมโครทางอ้อม
Ben Stiglitz

4
อาจซ้ำกันได้ของHow to concatenate two with the C preprocessor and expand a macro as in "arg ## _ ## MACRO"? เช่นเดียวกันกับมาโครใด ๆ นอกเหนือจากนั้น__LINE__(แม้ว่าจะเป็นกรณีการใช้งานทั่วไปก็ตาม
Ciro Santilli 郝海东冠状病六四事件法轮功

คำตอบ:


176

ปัญหาคือเมื่อคุณมีการแทนที่มาโครตัวประมวลผลก่อนจะขยายมาโครซ้ำได้ก็ต่อเมื่อไม่มีการใช้ตัวดำเนินการ stringizing #หรือตัวดำเนินการวางโทเค็น##กับมัน ดังนั้นคุณต้องใช้เลเยอร์พิเศษของการกำหนดทิศทางคุณสามารถใช้ตัวดำเนินการวางโทเค็นกับอาร์กิวเมนต์ที่ขยายซ้ำได้:

#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define UNIQUE static void TOKENPASTE2(Unique_, __LINE__)(void) {}

จากนั้น__LINE__ขยายเป็นหมายเลขบรรทัดระหว่างการขยายUNIQUE(เนื่องจากไม่เกี่ยวข้องกับอย่างใดอย่างหนึ่ง#หรือ##) จากนั้นการวางโทเค็นจะเกิดขึ้นระหว่างการขยายตัวของTOKENPASTE.

นอกจากนี้ควรสังเกตด้วยว่ายังมี__COUNTER__มาโครซึ่งจะขยายเป็นจำนวนเต็มใหม่ทุกครั้งที่มีการประเมินในกรณีที่คุณจำเป็นต้องมีUNIQUEแมโครหลายอินสแตนซ์ในบรรทัดเดียวกัน หมายเหตุ: __COUNTER__รองรับโดย MS Visual Studio, GCC (ตั้งแต่ V4.3) และ Clang แต่ไม่ใช่มาตรฐาน C


3
ฉันเกรงว่าจะใช้ไม่ได้กับ GNU cpp TOKENPASTE ใช้LINEเป็นตัวอักษร TOKENPASTE (Unique_, LINE ) ขยายเป็น Unique___LINE__
DD

3
@DD: D'oh แก้ไขแล้ว มันต้องมีทิศทาง 2 ชั้นไม่ใช่ 1
Adam Rosenfield

__COUNTER__แมโครไม่ทำงานสำหรับฉันใน GCC; แม้ว่าจะ__LINE__ได้ผลตามที่โฆษณาไว้ก็ตาม
Tyler

2
ข้อมูลเพิ่มเติมเล็กน้อยสำหรับทุกคนที่พยายามใช้COUNTERอ้างอิงจากmsdn.microsoft.com/en-us/library/b0084kay(v=vs.80).aspxเป็นมาโครเฉพาะสำหรับ Microsoft
Elva

3
มีคำอธิบายว่าทำไมคุณถึงต้องการ 2 ระดับของทิศทาง? ฉันได้ลองใช้เพียงครั้งเดียวไม่มี # และ ## และไม่ได้ขยายใน VS2017 เห็นได้ชัดว่า GCC เป็นเช่นเดียวกัน แต่ถ้าคุณเพิ่มทิศทางที่ 2 มันจะขยาย มายากล?
Gabe Halsmer

-3

GCC ไม่ต้องการ "การตัด" (หรือการตระหนัก) เว้นแต่ว่าผลลัพธ์จะต้องเป็น "สตริง" Gcc มีคุณสมบัติ แต่ทั้งหมดสามารถทำได้ด้วย C เวอร์ชัน 1 ธรรมดา (และบางคนโต้แย้งว่า Berkeley 4.3 C นั้นเร็วกว่ามากมันก็คุ้มค่าที่จะเรียนรู้วิธีใช้)

** เสียงดัง (llvm) ไม่ได้ทำพื้นที่สีขาวอย่างถูกต้องสำหรับการขยายมาโคร - มันเพิ่มช่องว่าง (ซึ่งแน่นอนว่าจะทำลายผลลัพธ์ที่เป็นตัวระบุ C สำหรับการประมวลผลล่วงหน้าต่อไป) ** เสียงดังไม่ได้ทำการขยายมาโคร # หรือ * ในฐานะ C Preprocessor คาดว่าจะมีมานานหลายทศวรรษ ตัวอย่างที่สำคัญคือการคอมไพล์ X11 มาโคร "Concat3" เสียผลตอนนี้คือ MISNAMED C Identifier ซึ่งแน่นอนว่าสร้างไม่สำเร็จ และฉันกำลังเริ่มสร้างสิ่งที่ล้มเหลวคืออาชีพของพวกเขา

ฉันคิดว่าคำตอบที่นี่คือ "C ใหม่ที่ทำลายมาตรฐานคือ C ที่ไม่ดี" แฮ็กเหล่านี้มักเลือกที่จะ (เนมสเปซของ clobber) พวกเขาเปลี่ยนค่าเริ่มต้นโดยไม่มีเหตุผล แต่ไม่ "ปรับปรุง C" จริงๆ (ยกเว้นตัวเองพูดเช่นนั้น: ซึ่งฉัน พูดคือการคุมกำเนิดทำขึ้นเพื่ออธิบายว่าทำไมพวกเขาถึงหนีไปพร้อมกับความแตกแยกทั้งหมดที่ยังไม่มีใครทำให้พวกเขาต้องรับผิดชอบ)


ไม่ใช่ปัญหาที่ C พรีโปรเซสเซอร์รุ่นก่อนหน้าไม่รองรับUNIq_ () __ เนื่องจากรองรับ #pragma ซึ่งอนุญาตให้ " แฮ็กเกอร์แบรนด์คอมไพเลอร์ในโค้ดถูกตั้งค่าสถานะเป็นแฮ็กเกอร์ " และยังทำงานได้ดีเช่นกันโดยไม่มีผลต่อมาตรฐานเช่นเดียวกับการเปลี่ยน ค่าเริ่มต้นคือความเสียหายที่ไม่มีประโยชน์และเช่นเดียวกับการเปลี่ยนสิ่งที่ฟังก์ชันทำในขณะที่ใช้ชื่อเดียวกัน (การโคลนเนมสเปซ) คือ ... มัลแวร์ในความคิดของฉัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.