ใน C คุณไม่สามารถนิยามฟังก์ชัน / การนำไปใช้ภายในไฟล์ส่วนหัว อย่างไรก็ตามใน C ++ คุณสามารถใช้วิธีการเต็มรูปแบบภายในไฟล์ส่วนหัว ทำไมพฤติกรรมต่างกัน
ใน C คุณไม่สามารถนิยามฟังก์ชัน / การนำไปใช้ภายในไฟล์ส่วนหัว อย่างไรก็ตามใน C ++ คุณสามารถใช้วิธีการเต็มรูปแบบภายในไฟล์ส่วนหัว ทำไมพฤติกรรมต่างกัน
คำตอบ:
ใน C ถ้าคุณกำหนดฟังก์ชั่นในไฟล์ส่วนหัวฟังก์ชั่นนั้นจะปรากฏในแต่ละโมดูลที่รวบรวมซึ่งรวมถึงไฟล์ส่วนหัวนั้นและสัญลักษณ์สาธารณะจะถูกส่งออกสำหรับฟังก์ชั่น ดังนั้นหากฟังก์ชั่น additup ถูกกำหนดไว้ใน header.h และ foo.c และ bar.c ทั้งสองรวมถึง header.h ดังนั้น foo.o และ bar.o จะรวมสำเนาของ additup
เมื่อคุณไปเชื่อมโยงสองไฟล์ออบเจ็กต์เหล่านั้นเข้าด้วยกันตัวเชื่อมโยงจะเห็นว่ามีการกำหนดสัญลักษณ์ additup มากกว่าหนึ่งครั้งและจะไม่อนุญาต
หากคุณประกาศให้ฟังก์ชั่นเป็นแบบคงที่จะไม่มีการส่งออกสัญลักษณ์ ไฟล์วัตถุ foo.o และ bar.o จะยังคงมีทั้งสำเนาแยกต่างหากของรหัสสำหรับฟังก์ชั่นและพวกเขาจะสามารถใช้งานได้ แต่ตัวเชื่อมโยงจะไม่สามารถเห็นสำเนาของฟังก์ชั่นใด ๆ ได้ จะไม่บ่น แน่นอนว่าไม่มีโมดูลอื่นใดที่จะสามารถเห็นฟังก์ชั่นได้เช่นกัน และโปรแกรมของคุณจะเต็มไปด้วยฟังก์ชันที่เหมือนกันสองชุด
หากคุณประกาศฟังก์ชั่นในไฟล์ส่วนหัวเท่านั้น แต่ไม่ต้องกำหนดฟังก์ชั่นและจากนั้นกำหนดไว้ในโมดูลเดียวจากนั้น linker จะเห็นหนึ่งสำเนาของฟังก์ชั่นและทุกโมดูลในโปรแกรมของคุณจะสามารถดูได้และ ใช้มัน. และโปรแกรมที่คอมไพล์ของคุณจะมีเพียงหนึ่งสำเนาของฟังก์ชั่น
ดังนั้นคุณสามารถมีฟังก์ชั่นการกำหนดในไฟล์ส่วนหัวใน C มันเป็นเพียงรูปแบบที่ไม่ดีรูปแบบที่ไม่ดีและความคิดที่ไม่ดีรอบตัว
(โดย "ประกาศ" ฉันหมายถึงให้ต้นแบบฟังก์ชั่นที่ไม่มีร่างกายโดย "กำหนด" ฉันหมายถึงให้รหัสที่แท้จริงของร่างกายฟังก์ชั่น; นี่คือคำศัพท์มาตรฐาน C)
#ifndef HEADER_H
ไม่ควรป้องกันอะไร
C และ C ++ มีพฤติกรรมเหมือนกันมากในเรื่องนี้ - คุณสามารถมีinline
ฟังก์ชั่นในส่วนหัว ใน C ++, วิธีการใด ๆ inline
ที่มีร่างกายที่อยู่ภายในกำหนดระดับคือโดยปริยาย ถ้าคุณต้องการที่จะทำเช่นเดียวกันใน C static inline
ประกาศฟังก์ชั่น
static inline
" ... และคุณจะยังคงมีฟังก์ชั่นหลายชุดในหน่วยการแปลแต่ละหน่วยที่ใช้งาน ใน C ++ ที่ไม่มีstatic
inline
ฟังก์ชั่นคุณจะมีเพียงหนึ่งสำเนา ในการมีการนำไปใช้จริงในส่วนหัวใน C คุณจะต้อง 1) ทำเครื่องหมายการนำไปใช้เป็นinline
(เช่นinline void func(){do_something();}
) และ 2) จริง ๆ แล้วบอกว่าฟังก์ชั่นนี้จะอยู่ในหน่วยการแปลเฉพาะบางหน่วย (เช่นvoid func();
)
แนวคิดของไฟล์ส่วนหัวต้องการคำอธิบายเล็กน้อย:
ไม่ว่าคุณจะให้ไฟล์ในบรรทัดคำสั่งของคอมไพเลอร์หรือทำ '#include' คอมไพเลอร์ส่วนใหญ่ยอมรับไฟล์คำสั่งที่มีนามสกุล c, c, cpp, c ++ เป็นต้นเป็นไฟล์ต้นฉบับ อย่างไรก็ตามพวกเขามักจะมีตัวเลือกบรรทัดคำสั่งเพื่อให้สามารถใช้งานส่วนขยายใดก็ได้ไปยังไฟล์ต้นฉบับได้
โดยทั่วไปไฟล์ที่ให้ในบรรทัดคำสั่งเรียกว่า 'แหล่งที่มา' และไฟล์ที่รวมอยู่เรียกว่า 'ส่วนหัว'
ขั้นตอนตัวประมวลผลล่วงหน้านำพวกเขาทั้งหมดไปแล้วทำให้ทุกอย่างดูเหมือนไฟล์ขนาดใหญ่เพียงไฟล์เดียวกับคอมไพเลอร์ สิ่งที่อยู่ในส่วนหัวหรือในแหล่งข้อมูลนั้นไม่เกี่ยวข้องในจุดนี้ โดยปกติจะมีตัวเลือกของคอมไพเลอร์ซึ่งสามารถแสดงผลลัพธ์ของขั้นตอนนี้
ดังนั้นสำหรับแต่ละไฟล์ที่ให้ไว้ในบรรทัดคำสั่งคอมไพเลอร์ไฟล์ขนาดใหญ่จะถูกกำหนดให้คอมไพเลอร์ อาจมีรหัส / ข้อมูลซึ่งจะใช้หน่วยความจำและ / หรือสร้างสัญลักษณ์ที่จะอ้างอิงจากไฟล์อื่น ตอนนี้สิ่งเหล่านี้จะสร้างภาพ 'วัตถุ' ตัวลิงก์สามารถให้ 'สัญลักษณ์ที่ซ้ำกัน' หากพบสัญลักษณ์เดียวกันในไฟล์วัตถุมากกว่าสองไฟล์ที่เชื่อมโยงเข้าด้วยกัน บางทีนี่อาจเป็นเหตุผล; ไม่แนะนำให้ใส่รหัสในไฟล์ส่วนหัวซึ่งสามารถสร้างสัญลักษณ์ในไฟล์วัตถุ
'อินไลน์' มักจะมีการ inline .. แต่เมื่อการดีบักพวกเขาอาจไม่ได้ inline ดังนั้นตัวเชื่อมโยงจึงไม่ให้ข้อผิดพลาดที่กำหนดทวีคูณ ง่าย ... นี่คือสัญลักษณ์ 'อ่อนแอ' และตราบใดที่ข้อมูล / รหัสสำหรับสัญลักษณ์ที่อ่อนแอจากวัตถุทั้งหมดมีขนาดและเนื้อหาเท่ากันลิงก์ที่เชื่อมโยงจะเก็บสำเนาหนึ่งชุดและคัดลอกจากวัตถุอื่น มันได้ผล.
คุณสามารถทำสิ่งนี้ได้ใน C99: inline
รับประกันว่าจะมีการจัดเตรียมฟังก์ชั่นไว้ที่อื่นดังนั้นหากฟังก์ชั่นไม่ได้ถูก inline คำจำกัดความของมันจะถูกแปลเป็นประกาศ (เช่นการนำไปปฏิบัติถูกทิ้ง) static
และแน่นอนคุณสามารถใช้
คำพูดมาตรฐาน C ++
C ++ 17 N4659 ร่างมาตรฐาน 10.1.6 "อินไลน์ระบุ" กล่าวว่าวิธีการที่ปริยายอินไลน์:
4 ฟังก์ชั่นที่กำหนดไว้ในการกำหนดชั้นเรียนเป็นฟังก์ชั่นแบบอิน
และยิ่งไปกว่านั้นเราเห็นว่าวิธีการแบบอินไลน์ไม่เพียง แต่สามารถทำได้ แต่ต้องกำหนดไว้ในหน่วยการแปลทั้งหมด:
6 ฟังก์ชั่นอินไลน์หรือตัวแปรจะถูกกำหนดไว้ในทุกหน่วยการแปลที่มันถูกใช้งานไม่ดีและจะต้องมีคำจำกัดความที่เหมือนกันในทุกกรณี (6.2)
สิ่งนี้ถูกกล่าวถึงอย่างชัดเจนในบันทึกย่อที่ 12.2.1 "ฟังก์ชั่นสมาชิก":
1 ฟังก์ชันสมาชิกอาจถูกกำหนด (11.4) ในคำจำกัดความของคลาสซึ่งในกรณีนี้มันเป็นฟังก์ชันสมาชิกแบบอินไลน์ (10.1.6) [... ]
3 [หมายเหตุ: สามารถมีได้อย่างน้อยหนึ่งคำจำกัดความของฟังก์ชั่นสมาชิกที่ไม่ใช่แบบอินไลน์ในโปรแกรม อาจมีนิยามฟังก์ชันสมาชิกแบบอินไลน์มากกว่าหนึ่งรายการในโปรแกรม ดู 6.2 และ 10.1.6 - บันทึกท้าย]
การใช้งาน GCC 8.3
main.cpp
struct MyClass {
void myMethod() {}
};
int main() {
MyClass().myMethod();
}
รวบรวมและดูสัญลักษณ์:
g++ -c main.cpp
nm -C main.o
เอาท์พุท:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 W MyClass::myMethod()
U __stack_chk_fail
0000000000000000 T main
จากนั้นเราจะเห็นman nm
ว่าMyClass::myMethod
สัญลักษณ์ถูกทำเครื่องหมายว่าอ่อนแอในไฟล์ออบเจกต์ ELF ซึ่งหมายความว่าสามารถปรากฏในไฟล์ออบเจ็กต์หลายไฟล์:
"W" "w" สัญลักษณ์เป็นสัญลักษณ์ที่อ่อนแอซึ่งไม่ได้ติดแท็กเป็นสัญลักษณ์ของวัตถุที่อ่อนแอโดยเฉพาะ เมื่อสัญลักษณ์ที่อ่อนแอถูกเชื่อมโยงกับสัญลักษณ์ที่กำหนดปกติจะใช้สัญลักษณ์ที่กำหนดปกติโดยไม่มีข้อผิดพลาด เมื่อสัญลักษณ์ที่ไม่ได้กำหนดอ่อนเชื่อมโยงและไม่มีการกำหนดสัญลักษณ์สัญลักษณ์ของค่าจะถูกกำหนดในลักษณะเฉพาะของระบบโดยไม่มีข้อผิดพลาด ในบางระบบตัวพิมพ์ใหญ่บ่งชี้ว่ามีการระบุค่าเริ่มต้น
อาจเป็นเพราะเหตุผลเดียวกับที่คุณต้องใส่วิธีการใช้งานเต็มรูปแบบในการกำหนดชั้นเรียนใน Java
พวกเขาอาจมีลักษณะคล้ายกันกับวงเล็บปีกกาและคำหลักเดียวกันจำนวนมาก แต่พวกเขาเป็นภาษาที่แตกต่างกัน