รวมถึงยาม
ในภาษาโปรแกรมCและC++ นั้น #include guardซึ่งบางครั้งเรียกว่าmacro guard , header guardหรือfile guardเป็นวิธีหนึ่งในการหลีกเลี่ยงปัญหาการรวมไฟล์ซ้ำซ้อนเมื่อใช้ คำ สั่งinclude
ตัวประมวลผลล่วงหน้าของภาษาซีจะประมวลผลคำสั่งการรวม เช่นการรวมไฟล์ Foo.h ในรูปแบบข้อความ และแปลงรหัสของไฟล์นั้นเข้าไปในสำเนาของไฟล์หลัก ซึ่งมักเรียกว่าหน่วยการแปล#include"Foo.h"
อย่างไรก็ตาม หาก#includeคำสั่งสำหรับไฟล์ใดไฟล์หนึ่งปรากฏหลายครั้งในระหว่างการคอมไพล์ โค้ดจะถูกทำซ้ำในไฟล์นั้นอย่างมีประสิทธิภาพ หากไฟล์ที่รวมเข้ามามีคำจำกัดความ อาจทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากกฎคำจำกัดความเดียว (One Definition Rule ) ซึ่งระบุว่าคำจำกัดความ (เช่น คำจำกัดความของคลาส) ไม่สามารถทำซ้ำในหน่วยการแปลได้#includeตัวป้องกันจะป้องกันสิ่งนี้โดยการกำหนดมาโครตัวประมวลผลล่วงหน้าเมื่อมีการรวมไฟล์ส่วนหัวเป็นครั้งแรก ในกรณีที่ไฟล์ส่วนหัวนั้นถูกรวมเข้ามาเป็นครั้งที่สอง#includeตัวป้องกันจะป้องกันไม่ให้โค้ดจริงภายในไฟล์ส่วนหัวนั้นถูกคอมไพล์
อีกทางเลือกหนึ่งนอกเหนือจากการ#includeใช้ guard คือ#pragma onceคำสั่งที่ไม่เป็นมาตรฐานแต่ได้รับการสนับสนุนอย่างแพร่หลายในคอมไพเลอร์ C และ C++ นี้ มีจุดประสงค์เดียวกันกับ#includeguard แต่ใช้โค้ดน้อยกว่าและไม่จำเป็นต้องกำหนดตัวแปร
โมดูลซึ่งถูกนำมาใช้ในC++20ช่วยขจัดความจำเป็นในการใช้#includeguard เนื่องจากตัวประมวลผลล่วงหน้าไม่ได้จัดการ guard โมดูลสามารถนำเข้าได้เพียงครั้งเดียวเท่านั้นในหน่วยการแปล
รวมสองรายการ
ตัวอย่าง
โค้ดภาษา C ต่อไปนี้แสดงให้เห็นถึงปัญหาที่เกิดขึ้นจริงหาก#includeไม่มีการตรวจสอบเงื่อนไข:
ไฟล์ "Grandparent.h"
struct Foo { int member ; };ไฟล์ "Parent.h"
#รวมไฟล์"Grandparent.h"ไฟล์ "Child.c"
#include "Grandparent.h" #include "Parent.h"ผลลัพธ์
// จาก "Grandparent.h" struct Foo { int member ; }; // จาก "Parent.h" struct Foo { int member ; };ในกรณีนี้ ไฟล์Child.cได้รวมข้อความจากไฟล์เฮดเดอร์Grandparent.h เข้ามาสองชุดโดยทางอ้อม ซึ่งทำให้เกิดข้อผิดพลาดในการคอมไพล์เนื่องจากชนิดโครงสร้างจะถูกกำหนดสองครั้ง ในภาษา C++ นี่เรียกว่าเป็นการละเมิดกฎการกำหนดเพียงครั้งเดียว (one definition rule )Foo
การใช้ #include guards
ตัวอย่าง
ใช้โค้ดเดียวกันกับในส่วนก่อนหน้า โดยเพิ่มเงื่อนไข การตรวจ #includeสอบ เข้าไป ตัวประมวลผลล่วงหน้าของภาษา Cจะประมวลผลไฟล์ส่วนหัว รวมทั้งประมวลผลซ้ำแบบเรียกซ้ำซึ่งจะทำให้ได้ไฟล์ต้นฉบับที่ใช้งานได้
ไฟล์ "Grandparent.h"
#ifndef GRANDPARENT_H #define GRANDPARENT_Hstruct Foo { int member ; };#endif // ปู่ย่าตายาย_Hไฟล์ "Parent.h"
#รวมไฟล์"Grandparent.h"ไฟล์ "Child.c"
#include "Grandparent.h" #include "Parent.h"ขั้นตอนกลาง
// เนื้อหาจาก "Grandparent.h" #ifndef GRANDPARENT_H // GRANDPARENT_H ไม่ได้ถูกกำหนดไว้#define GRANDPARENT_H// นิยามนี้ถูกคัดลอกมาstruct Foo { int member ; };#endif // ปู่ย่าตายาย_H// เนื้อหาจาก "Parent.h" #ifndef GRANDPARENT_H // GRANDPARENT_H ถูกกำหนดไว้แล้ว#define GRANDPARENT_H// นิยามนี้ไม่ได้ถูกคัดลอกมาstruct Foo { int member ; };#endif // ปู่ย่าตายาย_Hผลลัพธ์
struct Foo { int member ; };ในที่นี้ การรวม " Grandparent.h " ครั้งแรกมีการกำหนดมาโครไว้แล้ว เมื่อ " Child.c " รวม " Grandparent.h " เป็นครั้งที่สอง (ในขณะที่รวม " Parent.h " ด้วย) เนื่องจากการทดสอบส่งค่ากลับเป็นเท็จ ตัวประมวลผลล่วงหน้าจึงข้ามไปยังส่วนอื่นทำให้หลีกเลี่ยงการกำหนดมาโครครั้งที่สองโปรแกรมจึงคอมไพล์ได้อย่างถูกต้องGRANDPARENT_H#ifndef#endifstruct Foo
การอภิปราย
โปรแกรมเมอร์แต่ละคนอาจใช้รูปแบบการตั้งชื่อที่แตกต่างกัน สำหรับ มาโครป้องกัน รูปแบบ ทั่วไปอื่นๆ ของตัวอย่างข้างต้น ได้แก่( โดยแทนที่ข้อมูลเวลาที่เหมาะสม) และชื่อที่สร้างจากUUID (อย่างไรก็ตามชื่อที่ขึ้นต้นด้วยเครื่องหมายขีดล่างหนึ่งตัวและตัวอักษรตัวใหญ่ (C และ C++) หรือชื่อใดๆ ที่มีเครื่องหมายขีดล่างสองตัว (เฉพาะ C++) เช่นและสงวนไว้สำหรับการใช้งานภาษา และผู้ใช้ไม่ควรใช้[ 1 ] [ 2 ] )GRANDPARENT_INCLUDEDCREATORSNAME_YYYYMMDD_HHMMSS_GRANDPARENT_HGRANDPARENT__H
แน่นอนว่า สิ่งสำคัญคือต้องหลีกเลี่ยงการใช้ชื่อมาโคร include-guard ซ้ำกันในไฟล์เฮดเดอร์ต่าง ๆ เพราะการรวมไฟล์แรกจะทำให้ไฟล์ที่สองไม่สามารถรวมได้ ส่งผลให้สูญเสียการประกาศ คำจำกัดความแบบอินไลน์ หรือสิ่งอื่น ๆ#includeในไฟล์เฮดเดอร์ที่สอง
ความยากลำบาก
เพื่อให้#includeการ์ดทำงานได้อย่างถูกต้อง การ์ดแต่ละตัวจะต้องทดสอบและตั้งค่ามาโครพรีโปรเซสเซอร์ที่แตกต่างกันตามเงื่อนไข ดังนั้น โครงการที่ใช้#includeการ์ดจะต้องกำหนดรูปแบบการตั้งชื่อที่สอดคล้องกันสำหรับการ์ดการรวม และตรวจสอบให้แน่ใจว่ารูปแบบดังกล่าวไม่ขัดแย้งกับรูปแบบของส่วนหัวของบุคคลที่สามที่ใช้ หรือกับชื่อของมาโครที่มองเห็นได้ทั่วโลก
ด้วยเหตุนี้ การใช้งาน C และ C++ ส่วนใหญ่จึงมีคำสั่งที่ไม่เป็นมาตรฐาน#pragma onceคำสั่งนี้ เมื่อแทรกไว้ที่ด้านบนของไฟล์ส่วนหัว จะทำให้มั่นใจได้ว่าไฟล์นั้นจะถูกรวมเพียงครั้งเดียว ภาษา Objective-C (ซึ่งเป็นซูเปอร์เซ็ตของ C) มี#importคำสั่ง ซึ่งทำงานเหมือนกับคำสั่ง ทุกประการ#includeยกเว้นว่าจะรวมแต่ละไฟล์เพียงครั้งเดียว จึงไม่จำเป็นต้องใช้#includeตัวป้องกัน[ 3 ]
ภาษาอื่นๆ
บางภาษาโปรแกรมรองรับการระบุว่าควรแทรกโค้ดเพียงครั้งเดียวในไฟล์ที่แทรก แทนที่จะเป็นในไฟล์ที่ถูกแทรก (เช่นเดียวกับ include guards ในภาษา C/C++ #pragma once):
- PL/Iใช้
%INCLUDEคำสั่งดังกล่าวเทียบเท่ากับ#includeคำสั่งในภาษา C IBM Enterprise PL/I ยังรองรับ%XINCLUDEคำสั่งที่จะ "รวมข้อความภายนอกเข้ากับโปรแกรมต้นฉบับหากยังไม่ได้รวมไว้ก่อนหน้านี้" (นอกจากนี้ยังมีXPROCEDUREคำสั่งที่คล้ายกับPROCEDUREคำสั่ง ซึ่งจะละเว้นการปรากฏครั้งที่สองและครั้งต่อๆ ไปของ ที่XPROCEDUREมีชื่อเดียวกัน) [ 4 ] - คำสั่ง ของObjective-C
#import(ดูด้านบน) - PHPของ
include_once[ 5 ]
ดูเพิ่มเติม
ลิงก์ภายนอก
- รวมการเพิ่มประสิทธิภาพการป้องกัน
- ส่วนเกินรวมถึงยาม