กลับไปหน้าบทความ

อ่าน 3 นาที

รวมถึงยาม

ในภาษาโปรแกรมCและC++ นั้น #include guardซึ่งบางครั้งเรียกว่าmacro guard , header guardหรือfile guardเป็นวิธีหนึ่งในการหลีกเลี่ยงปัญหาการรวมไฟล์ซ้ำซ้อนเมื่อใช้ คำ สั่งinclude

รวมถึงยาม

ในภาษาโปรแกรม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 ]

ดูเพิ่มเติม

  • รวมการเพิ่มประสิทธิภาพการป้องกัน
  • ส่วนเกินรวมถึงยาม

สรุปเนื้อหา

ข้อมูลสำคัญจากบทความ

ข้อมูลสำคัญเกี่ยวกับ รวมถึงยาม

ในภาษาโปรแกรมCและC++ นั้น #include guardซึ่งบางครั้งเรียกว่าmacro guard , header guardหรือfile guardเป็นวิธีหนึ่งในการหลีกเลี่ยงปัญหาการรวมไฟล์ซ้ำซ้อนเมื่อใช้ คำ สั่งinclude

ตัวอย่าง

โค้ดภาษา C ต่อไปนี้แสดงให้เห็นถึงปัญหาที่เกิดขึ้นจริงหาก #include ไม่มีการตรวจสอบเงื่อนไข:

การอภิปราย

โปรแกรมเมอร์ แต่ละคนอาจใช้ รูปแบบการตั้งชื่อ ที่แตกต่างกัน สำหรับ มาโคร ป้องกัน รูปแบบ ทั่วไปอื่นๆ ของตัวอย่างข้างต้น ได้แก่( โดยแทนที่ข้อมูลเวลาที่เหมาะสม) และชื่อที่สร้างจาก UUID (อย่างไรก็ตาม ชื่อ ที่ขึ้นต้นด้วยเครื่องหมายขีดล่างหนึ่งตัวและ ตัวอักษรตัวใหญ่...

ความยากลำบาก

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