ตัวประมวลผลล่วงหน้ามาตรฐาน
$ 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 หนึ่ง (มักจะมีวิธีการกำหนดค่าคอมไพเลอร์อย่างเหมาะสม) กว่า ใช้เวลามากในการพยายามหาวิธีในการทำงาน