แอปพลิเคชันของตัวดำเนินการประมวลผล ## preprocessor และ gotchas ที่ต้องพิจารณามีอะไรบ้าง?


88

ดังที่ได้กล่าวไว้ในคำถามก่อนหน้านี้หลายข้อฉันกำลังทำงานผ่าน K&R และกำลังอยู่ในพรีโปรเซสเซอร์ สิ่งที่น่าสนใจอีกอย่างหนึ่ง - สิ่งที่ฉันไม่เคยรู้มาก่อนจากการพยายามเรียนรู้ C - ก่อนหน้านี้คือ##ตัวดำเนินการก่อนโปรเซสเซอร์ ตาม K&R:

ตัวดำเนินการตัวประมวลผลก่อน## จัดเตรียมวิธีการต่ออาร์กิวเมนต์ที่แท้จริงระหว่างการขยายมาโคร หากพารามิเตอร์ในข้อความแทนที่อยู่ติดกับ a ##พารามิเตอร์จะถูกแทนที่ด้วยอาร์กิวเมนต์จริง ##และช่องว่างรอบ ๆ จะถูกลบออกและผลลัพธ์จะถูกสแกนใหม่ ตัวอย่างเช่นแมโครpaste เชื่อมสองอาร์กิวเมนต์:

#define paste(front, back) front ## back

เพื่อสร้างโทเค็นpaste(name, 1) name1

ทำไมคนถึงใช้สิ่งนี้ในโลกแห่งความเป็นจริง ตัวอย่างการใช้งานจริงมีอะไรบ้างและมีข้อควรพิจารณาหรือไม่?

คำตอบ:


47

CrashRpt: การใช้ ## เพื่อแปลงสตริงมาโครหลายไบต์เป็น Unicode

การใช้งานที่น่าสนใจใน CrashRpt (ไลบรารีการรายงานข้อขัดข้อง) มีดังต่อไปนี้:

ที่นี่พวกเขาต้องการใช้สตริงสองไบต์แทนสตริงหนึ่งไบต์ต่อถ่าน นี่อาจดูเหมือนว่าไม่มีจุดหมายจริงๆ แต่พวกเขาทำด้วยเหตุผลที่ดี

ใช้กับมาโครอื่นที่ส่งคืนสตริงพร้อมวันที่และเวลา

การวางไว้Lข้าง a __ DATE __จะทำให้คุณมีข้อผิดพลาดในการคอมไพล์


Windows: การใช้ ## สำหรับสตริง Unicode ทั่วไปหรือหลายไบต์

Windows ใช้สิ่งต่อไปนี้:

และ_Tใช้ทุกที่ในรหัส


ไลบรารีต่างๆที่ใช้สำหรับชื่อตัวเข้าถึงและตัวปรับแต่งที่สะอาด:

ฉันเคยเห็นมันใช้ในรหัสเพื่อกำหนดตัวเข้าถึงและตัวปรับแต่ง:

ในทำนองเดียวกันคุณสามารถใช้วิธีเดียวกันนี้สำหรับการสร้างชื่อที่ชาญฉลาดประเภทอื่น ๆ


ไลบรารีต่างๆโดยใช้เพื่อทำการประกาศตัวแปรหลายตัวพร้อมกัน:


3
เนื่องจากคุณสามารถเชื่อมต่อตัวอักษรสตริงในเวลาคอมไพล์คุณจึงสามารถลดนิพจน์ BuildDate เป็นstd::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); และสร้างสตริงทั้งหมดพร้อมกันโดยปริยาย
user666412

49

สิ่งหนึ่งที่ควรระวังเมื่อคุณใช้ตัวดำเนินการก่อนการประมวลผลtoken-paste (' ##') หรือ stringizing (' #') คือคุณต้องใช้อินดิเรเตอร์ในระดับพิเศษเพื่อให้ทำงานได้อย่างถูกต้องในทุกกรณี

หากคุณไม่ทำเช่นนี้และรายการที่ส่งไปยังตัวดำเนินการวางโทเค็นเป็นมาโครเองคุณจะได้ผลลัพธ์ที่อาจไม่ใช่สิ่งที่คุณต้องการ:

ผลลัพธ์:


1
สำหรับคำอธิบายเกี่ยวกับพฤติกรรมของตัวประมวลผลก่อนหน้านี้โปรดดูstackoverflow.com/questions/8231966/…
Adam Davis

@MichaelBurr ฉันกำลังอ่านคำตอบของคุณและฉันมีข้อสงสัย ทำไมLINE ถึงพิมพ์หมายเลขบรรทัด?
HELP PLZ

3
@AbhimanyuAryan: ฉันไม่แน่ใจว่านี่คือสิ่งที่คุณถามหรือเปล่า แต่__LINE__เป็นชื่อมาโครพิเศษที่ถูกแทนที่ด้วยตัวประมวลผลล่วงหน้าด้วยหมายเลขบรรทัดปัจจุบันของไฟล์ต้นฉบับ
Michael Burr

มันจะดีมากถ้าสามารถอ้างอิง / เชื่อมโยงข้อกำหนดภาษาได้ดังที่นี่
Antonio

14

นี่คือ gotcha ที่ฉันพบเมื่ออัปเกรดเป็นคอมไพเลอร์เวอร์ชันใหม่:

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

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

ตัวอย่างเช่นอาจพยายามสร้างตัวอักษรสตริงในเวลาคอมไพล์โดยใช้ตัวดำเนินการวางโทเค็น:

ในคอมไพเลอร์บางตัวสิ่งนี้จะให้ผลลัพธ์ที่คาดหวัง:

ในคอมไพเลอร์อื่น ๆ จะรวมถึงช่องว่างที่ไม่ต้องการ:

GCC เวอร์ชันที่ทันสมัยพอสมควร (> = 3.3 หรือมากกว่านั้น) จะไม่สามารถรวบรวมรหัสนี้ได้:

วิธีแก้ปัญหาคือการละเว้นตัวดำเนินการวางโทเค็นเมื่อเชื่อมต่อโทเค็นตัวประมวลผลล่วงหน้ากับตัวดำเนินการ C / C ++

เอกสารบท GCC CPP ในการเรียงต่อกันมีข้อมูลที่เป็นประโยชน์เพิ่มเติมเกี่ยวกับผู้ประกอบการ token-วาง


ขอบคุณ - ฉันไม่รู้เรื่องนี้ (แต่ฉันก็ไม่ได้ใช้ตัวดำเนินการก่อนการประมวลผลเหล่านี้มากเกินไป ... )
Michael Burr

3
เรียกว่าโอเปอเรเตอร์ "การวางโทเค็น" ด้วยเหตุผล - จุดประสงค์คือการลงเอยด้วยโทเค็นเดียวเมื่อคุณทำเสร็จแล้ว เขียนดีมาก
Mark Ransom

เมื่อผลลัพธ์ของตัวดำเนินการวางโทเค็นไม่ใช่โทเค็นตัวประมวลผลล่วงหน้าที่ถูกต้องพฤติกรรมจะไม่ถูกกำหนด
alecov

การเปลี่ยนแปลงของภาษาเช่นเลขฐานสิบหกหรือ (ใน C ++) ตัวคั่นหลักและตัวอักษรที่ผู้ใช้กำหนดเองเปลี่ยนสิ่งที่ถือว่าเป็น "โทเค็นก่อนการประมวลผลที่ถูกต้อง" อย่างต่อเนื่องดังนั้นโปรดอย่าละเมิดเช่นนั้น! หากคุณต้องแยกโทเค็น (ภาษาที่เหมาะสม) โปรดสะกดเป็นโทเค็นสองอันแยกกันและอย่าอาศัยการโต้ตอบโดยบังเอิญระหว่างไวยากรณ์ของตัวประมวลผลก่อนและภาษาที่เหมาะสม
Kerrek SB

6

สิ่งนี้มีประโยชน์ในทุกสถานการณ์เพื่อไม่ให้ตัวเองทำซ้ำโดยไม่จำเป็น ต่อไปนี้เป็นตัวอย่างจากซอร์สโค้ด Emacs เราต้องการโหลดฟังก์ชันจำนวนหนึ่งจากไลบรารี ควรกำหนดฟังก์ชัน "foo" ให้fn_fooและอื่น ๆ เรากำหนดมาโครต่อไปนี้:

จากนั้นเราสามารถใช้:

ข้อดีคือไม่ต้องเขียนทั้งสองอย่างfn_XpmFreeAttributesและ"XpmFreeAttributes"(และเสี่ยงต่อการสะกดผิดอย่างใดอย่างหนึ่ง)


4

คำถามก่อนหน้านี้ใน Stack Overflow ถามถึงวิธีการที่ราบรื่นในการสร้างการแทนค่าสตริงสำหรับค่าคงที่การนับโดยไม่ต้องพิมพ์ซ้ำบ่อยมาก

ลิงค์

คำตอบของฉันสำหรับคำถามนั้นแสดงให้เห็นว่าการใช้เวทมนตร์ก่อนตัวประมวลผลเล็กน้อยช่วยให้คุณสามารถกำหนดการแจงนับของคุณเช่นนี้ได้อย่างไร (ตัวอย่าง) ... ;

... ด้วยประโยชน์ที่การขยายมาโครไม่เพียง แต่กำหนดการแจงนับ (ในไฟล์. h) เท่านั้น แต่ยังกำหนดอาร์เรย์ของสตริงที่ตรงกัน (ในไฟล์. c)

ชื่อของตารางสตริงมาจากการวางพารามิเตอร์มาโคร (เช่นสี) ไปยัง StringTable โดยใช้ตัวดำเนินการ ## แอปพลิเคชั่น (เทคนิค?) เช่นนี้คือสิ่งที่ตัวดำเนินการ # และ ## เป็นสิ่งล้ำค่า


3

คุณสามารถใช้การวางโทเค็นเมื่อคุณต้องการเชื่อมต่อพารามิเตอร์มาโครกับอย่างอื่น

สามารถใช้สำหรับเทมเพลต:

ในกรณีนี้ LINKED_LIST (int) จะให้คุณ

ในทำนองเดียวกันคุณสามารถเขียนเทมเพลตฟังก์ชันสำหรับการส่งผ่านรายการได้


2

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

ขยายเป็นดังนี้:

สิ่งนี้บังคับใช้การกำหนดพารามิเตอร์ที่ถูกต้องสำหรับอ็อบเจ็กต์ "ที่ได้รับ" ทั้งหมดเมื่อคุณทำ:

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


2

SGlibใช้ ## เพื่อฟัดจ์เทมเพลตใน C. เนื่องจากไม่มีฟังก์ชันโอเวอร์โหลด ## จึงใช้เพื่อติดชื่อประเภทลงในชื่อของฟังก์ชันที่สร้างขึ้น ถ้าฉันมีประเภทรายการที่เรียกว่า list_t ฉันจะได้รับฟังก์ชั่นที่มีชื่อว่า sglib_list_t_concat และอื่น ๆ


2

ฉันใช้มันสำหรับการยืนยันที่บ้านในคอมไพเลอร์ C ที่ไม่ได้มาตรฐานสำหรับฝังตัว:


3
ฉันคิดว่าคุณหมายถึง 'ไม่ได้มาตรฐาน' ที่คอมไพเลอร์ไม่ได้ทำการวางสตริง แต่ทำการวางโทเค็น - หรือจะทำงานได้โดยไม่ต้อง##?
PJTraill

1

ฉันใช้เพื่อเพิ่มคำนำหน้าแบบกำหนดเองให้กับตัวแปรที่กำหนดโดยมาโคร สิ่งที่ชอบ:

ขยายเป็น:


1

การใช้งานหลักคือเมื่อคุณมีหลักการตั้งชื่อและคุณต้องการให้มาโครของคุณใช้ประโยชน์จากหลักการตั้งชื่อนั้น บางทีคุณอาจมีวิธีการหลายตระกูล: image_create (), image_activate () และ image_release () รวมถึง file_create (), file_activate (), file_release () และ mobile_create (), mobile_activate () และ mobile_release ()

คุณสามารถเขียนมาโครสำหรับจัดการวงจรชีวิตของวัตถุ:

แน่นอนว่า "รุ่นที่น้อยที่สุดของออบเจ็กต์" ไม่ใช่รูปแบบเดียวของการตั้งชื่อที่ใช้กับหลักการตั้งชื่อส่วนใหญ่เกือบทั้งหมดใช้สตริงย่อยทั่วไปเพื่อสร้างชื่อ ชื่อฟังก์ชัน (ตามด้านบน) หรือชื่อฟิลด์ชื่อตัวแปรหรือสิ่งอื่น ๆ ส่วนใหญ่ให้ฉันได้


1

การใช้งานที่สำคัญอย่างหนึ่งใน WinCE:

ในขณะที่กำหนดคำอธิบายบิตรีจิสเตอร์เราทำดังต่อไปนี้:

และในขณะที่ใช้ BITFMASK เพียงใช้:


0

มีประโยชน์มากสำหรับการตัดไม้ คุณทำได้:

หรือถ้าคอมไพเลอร์ของคุณไม่รองรับฟังก์ชันและfunc :

ข้อความ "ฟังก์ชัน" ข้างต้นจะบันทึกข้อความและแสดงว่าฟังก์ชันใดบันทึกข้อความ

ไวยากรณ์ C ++ ของฉันอาจไม่ถูกต้องนัก


1
คุณกำลังพยายามทำอะไรอยู่? มันจะทำงานได้ดีเช่นกันหากไม่มี "##" เนื่องจากไม่จำเป็นต้องโทเค็นวาง "" เป็น "ข้อความ" คุณพยายามที่จะสตริงข้อความ msg หรือไม่? นอกจากนี้FILEและLINEต้องเป็นตัวพิมพ์ใหญ่ไม่ใช่ตัวพิมพ์เล็ก
bk1e

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