วิธีเชื่อมต่อสองครั้งด้วยตัวประมวลผลล่วงหน้า C และขยายแมโครดังเช่นใน“ arg ## _ ## MACRO”


152

ฉันพยายามเขียนโปรแกรมที่ชื่อของฟังก์ชั่นบางอย่างขึ้นอยู่กับค่าของตัวแปรมาโครที่มีมาโครเช่นนี้

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

น่าเสียดายที่แมโครNAME()กลายเป็นสิ่งนั้น

int some_function_VARIABLE(int a);

ค่อนข้างมากกว่า

int some_function_3(int a);

ดังนั้นนี่เป็นวิธีที่ผิดอย่างชัดเจนที่จะไปเกี่ยวกับมัน โชคดีที่จำนวนของค่าที่เป็นไปได้ที่แตกต่างกันสำหรับ VARIABLE มีขนาดเล็กดังนั้นฉันจึงสามารถทำ#if VARIABLE == nและแยกรายการเคสทั้งหมด แต่ฉันสงสัยว่ามีวิธีที่ชาญฉลาดในการทำหรือไม่


3
คุณแน่ใจหรือว่าไม่ต้องการใช้พอยน์เตอร์ของฟังก์ชั่นแทน?
György Andrasek

8
@Jurily - ตัวชี้ฟังก์ชันทำงานที่รันไทม์ตัวประมวลผลล่วงหน้าทำงานที่ (ก่อน) รวบรวมเวลา มีความแตกต่างแม้ว่าทั้งสองสามารถใช้สำหรับงานเดียวกันได้
คริสลัทซ์

1
ประเด็นก็คือสิ่งที่มันใช้ในการเป็นห้องสมุดเรขาคณิตคำนวณอย่างรวดเร็ว .. ซึ่งเดินสายสำหรับมิติที่แน่นอน อย่างไรก็ตามบางคนอาจต้องการใช้กับมิติที่แตกต่างกันเล็กน้อย (เช่น 2 และ 3) และดังนั้นจึงต้องการวิธีที่ง่ายในการสร้างรหัสที่มีฟังก์ชันตามมิติและชื่อประเภท นอกจากนี้โค้ดจะถูกเขียนใน ANSI C ดังนั้นเนื้อหา C ++ ที่มีเทมเพลตและความเชี่ยวชาญไม่สามารถใช้ได้ที่นี่
JJ

2
การลงคะแนนเพื่อเปิดอีกครั้งเนื่องจากคำถามนี้เฉพาะเกี่ยวกับการขยายแมโครแบบเรียกซ้ำและstackoverflow.com/questions/216875/using-in-macrosเป็นคำทั่วไป "เป็นสิ่งที่ดี" ชื่อของคำถามนี้ควรทำให้แม่นยำยิ่งขึ้น
Ciro Santilli 郝海东冠状病六四事件法轮功

ฉันหวังว่าตัวอย่างนี้จะถูกย่อให้เล็กสุด: เกิดขึ้นกับ#define A 0 \n #define M a ## A: มีสอง##ไม่ใช่กุญแจ
Ciro Santilli 法轮功冠状病六四事件法轮功

คำตอบ:


223

ตัวประมวลผลล่วงหน้ามาตรฐาน

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

อ้อมสองระดับ

ในการแสดงความคิดเห็นต่อคำตอบอื่นCade Roux ถามว่าทำไมสิ่งนี้จึงต้องการการอ้อมสองระดับ คำตอบ flippant เป็นเพราะนั่นคือวิธีที่มาตรฐานต้องการให้มันทำงาน คุณมักจะพบว่าคุณต้องการเคล็ดลับที่เทียบเท่ากับตัวดำเนินการ stringizing เช่นกัน

ส่วนที่ 6.10.3 ของมาตรฐาน C99 ครอบคลุม 'การแทนที่แมโคร' และ 6.10.3.1 ครอบคลุม 'การทดแทนอาร์กิวเมนต์'

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

ในการภาวนาNAME(mine)อาร์กิวเมนต์คือ 'เหมือง'; มันขยายอย่างเต็มที่เป็น 'ฉัน'; มันจะถูกแทนที่ลงในสตริงการแทนที่:

EVALUATOR(mine, VARIABLE)

ตอนนี้แมโคร EVALUATOR ถูกค้นพบแล้วและอาร์กิวเมนต์จะถูกแยกออกเป็น 'เหมือง' และ 'ตัวแปร' หลังถูกขยายจนเต็มเป็น '3' และแทนที่ลงในสตริงการแทนที่:

PASTER(mine, 3)

การดำเนินการนี้ครอบคลุมโดยกฎอื่น ๆ (6.10.3.3 'ตัวดำเนินการ ##'):

หากในรายการการแทนที่ของมาโครคล้ายฟังก์ชันพารามิเตอร์จะถูกนำหน้าหรือตามด้วย##โทเค็นการประมวลผลล่วงหน้าพารามิเตอร์จะถูกแทนที่ด้วยลำดับโทเค็นการประมวลผลของอาร์กิวเมนต์ที่สอดคล้องกัน [ ... ]

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

ดังนั้นรายการทดแทนมีxตามด้วย##และ##ตามด้วยy; ดังนั้นเราจึงมี:

mine ## _ ## 3

และกำจัด##โทเค็นและเชื่อมโทเค็นทั้งสองข้างเข้าด้วยกัน 'mine' กับ '_' และ '3' เพื่อให้ได้ผลลัพธ์:

mine_3

นี่คือผลลัพธ์ที่ต้องการ


หากเราดูคำถามเดิมรหัสนั้น (ปรับให้ใช้ 'ของฉัน' แทน 'some_function'):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

อาร์กิวเมนต์ของ NAME นั้นชัดเจนว่าเป็นของฉันและมีการขยายอย่างเต็มที่
ทำตามกฎของ 6.10.3.3 เราพบ:

mine ## _ ## VARIABLE

ซึ่งเมื่อตัว##ดำเนินการถูกกำจัดให้ทำแผนที่เพื่อ:

mine_VARIABLE

ตรงตามที่รายงานไว้ในคำถาม


ตัวประมวลผลล่วงหน้า C แบบดั้งเดิม

Robert Rüger ถาม :

มีวิธีใด ๆ นี้กับ preprocessor C แบบดั้งเดิมซึ่งไม่ได้มีผู้ประกอบการวางโทเค็น##?

อาจจะและอาจจะไม่ - มันขึ้นอยู่กับตัวประมวลผลล่วงหน้า ข้อดีอย่างหนึ่งของ preprocessor มาตรฐานคือมันมีระบบอำนวยความสะดวกที่ทำงานได้อย่างน่าเชื่อถือในขณะที่มีการใช้งานที่แตกต่างกันสำหรับ preprocessor มาตรฐานก่อน ข้อกำหนดหนึ่งคือเมื่อตัวประมวลผลล่วงหน้าแทนที่ความคิดเห็นจะไม่สร้างช่องว่างเนื่องจากตัวประมวลผลล่วงหน้า ANSI จำเป็นต้องทำ ตัวประมวลผลล่วงหน้า GCC (6.3.0) C ตรงตามข้อกำหนดนี้ ตัวประมวลผลล่วงหน้าของ Clang จาก XCode 8.2.1 ไม่มี

เมื่อใช้งานได้สิ่งนี้จะทำงาน ( x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

โปรดทราบว่าไม่มีช่องว่างระหว่างfun,และVARIABLE- ซึ่งมีความสำคัญเพราะถ้ามีจะถูกคัดลอกไปยังเอาต์พุตและท้ายที่สุดคุณจะmine_ 3ใช้ชื่อซึ่งไม่ถูกต้องตามหลักไวยากรณ์ (ตอนนี้ฉันขอคืนผมได้ไหม?)

ด้วย GCC 6.3.0 (ทำงานcpp -traditional x-paste.c) ฉันได้รับ:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

ด้วย Clang จาก XCode 8.2.1 ฉันได้รับ:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

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

อาจมีวิธีอื่นในการทำเช่นนี้ อย่างไรก็ตามวิธีนี้ใช้ไม่ได้:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC สร้าง:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

ปิด แต่ไม่มีลูกเต๋า แน่นอนว่า YMMV ขึ้นอยู่กับ preprocessor มาตรฐานที่คุณใช้ ตรงไปตรงมาถ้าคุณติดกับ preprocessor ที่ไม่ให้ความร่วมมือมันอาจจะง่ายกว่าที่จะจัดให้ใช้ preprocessor C มาตรฐานแทนหนึ่ง pre-standard หนึ่ง (มักจะมีวิธีการกำหนดค่าคอมไพเลอร์อย่างเหมาะสม) กว่า ใช้เวลามากในการพยายามหาวิธีในการทำงาน


1
ใช่สิ่งนี้แก้ปัญหาได้ ฉันรู้เคล็ดลับที่มีการเรียกซ้ำสองระดับ - ฉันต้องเล่นด้วยการทำให้เป็นสตริงอย่างน้อยหนึ่งครั้ง - แต่ไม่รู้ว่าจะทำสิ่งนี้ได้อย่างไร
JJ

มีวิธีใดบ้างในการทำเช่นนี้กับpreprocessor C แบบดั้งเดิมซึ่งไม่มีตัวดำเนินการโทเค็นการวาง ##?
Robert Rüger

1
@ RobertRüger: มันคู่ความยาวของคำตอบ cpp -traditionalแต่ผมได้เพิ่มข้อมูลไปยังหน้าปก โปรดทราบว่าไม่มีคำตอบที่ชัดเจน - ขึ้นอยู่กับตัวประมวลผลล่วงหน้าที่คุณมี
Jonathan Leffler

ขอบคุณมากสำหรับคำตอบ มันยอดเยี่ยมมาก! ในขณะเดียวกันฉันก็พบวิธีแก้ปัญหาที่แตกต่างออกไปเล็กน้อย ดูที่นี่ นอกจากนี้ยังมีปัญหาที่มันไม่ทำงานกับเสียงดังกราวแม้ว่า โชคดีที่ไม่เป็นปัญหาสำหรับการใช้งานของฉัน ...
โรเบิร์ต Ruger

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

สุจริตคุณไม่ต้องการที่จะรู้ว่าทำไมงานนี้ ถ้าคุณรู้ว่าทำไมมันถึงได้ผลคุณจะกลายเป็นคนที่ทำงานที่รู้เรื่องแบบนี้และทุกคนจะมาถามคุณ =)

แก้ไข:หากคุณต้องการทราบว่าทำไมมันถึงใช้งานได้จริงฉันจะโพสต์คำอธิบายอย่างมีความสุขโดยสมมติว่าไม่มีใครชนะฉัน


คุณช่วยอธิบายได้ไหมว่าทำไมมันถึงต้องการการเปลี่ยนทิศทางสองระดับ ฉันได้รับคำตอบด้วยการเปลี่ยนเส้นทางในระดับหนึ่ง แต่ฉันลบคำตอบออกไปเพราะฉันต้องติดตั้ง C ++ ลงใน Visual Studio ของฉันแล้วมันก็ไม่ทำงาน
Cade Roux
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.