เหตุใดคุณจึงมีคำนิยามวิธีการภายในไฟล์ส่วนหัวใน C ++ เมื่ออยู่ใน C คุณไม่สามารถ?


23

ใน C คุณไม่สามารถนิยามฟังก์ชัน / การนำไปใช้ภายในไฟล์ส่วนหัว อย่างไรก็ตามใน C ++ คุณสามารถใช้วิธีการเต็มรูปแบบภายในไฟล์ส่วนหัว ทำไมพฤติกรรมต่างกัน

คำตอบ:


28

ใน C ถ้าคุณกำหนดฟังก์ชั่นในไฟล์ส่วนหัวฟังก์ชั่นนั้นจะปรากฏในแต่ละโมดูลที่รวบรวมซึ่งรวมถึงไฟล์ส่วนหัวนั้นและสัญลักษณ์สาธารณะจะถูกส่งออกสำหรับฟังก์ชั่น ดังนั้นหากฟังก์ชั่น additup ถูกกำหนดไว้ใน header.h และ foo.c และ bar.c ทั้งสองรวมถึง header.h ดังนั้น foo.o และ bar.o จะรวมสำเนาของ additup

เมื่อคุณไปเชื่อมโยงสองไฟล์ออบเจ็กต์เหล่านั้นเข้าด้วยกันตัวเชื่อมโยงจะเห็นว่ามีการกำหนดสัญลักษณ์ additup มากกว่าหนึ่งครั้งและจะไม่อนุญาต

หากคุณประกาศให้ฟังก์ชั่นเป็นแบบคงที่จะไม่มีการส่งออกสัญลักษณ์ ไฟล์วัตถุ foo.o และ bar.o จะยังคงมีทั้งสำเนาแยกต่างหากของรหัสสำหรับฟังก์ชั่นและพวกเขาจะสามารถใช้งานได้ แต่ตัวเชื่อมโยงจะไม่สามารถเห็นสำเนาของฟังก์ชั่นใด ๆ ได้ จะไม่บ่น แน่นอนว่าไม่มีโมดูลอื่นใดที่จะสามารถเห็นฟังก์ชั่นได้เช่นกัน และโปรแกรมของคุณจะเต็มไปด้วยฟังก์ชันที่เหมือนกันสองชุด

หากคุณประกาศฟังก์ชั่นในไฟล์ส่วนหัวเท่านั้น แต่ไม่ต้องกำหนดฟังก์ชั่นและจากนั้นกำหนดไว้ในโมดูลเดียวจากนั้น linker จะเห็นหนึ่งสำเนาของฟังก์ชั่นและทุกโมดูลในโปรแกรมของคุณจะสามารถดูได้และ ใช้มัน. และโปรแกรมที่คอมไพล์ของคุณจะมีเพียงหนึ่งสำเนาของฟังก์ชั่น

ดังนั้นคุณสามารถมีฟังก์ชั่นการกำหนดในไฟล์ส่วนหัวใน C มันเป็นเพียงรูปแบบที่ไม่ดีรูปแบบที่ไม่ดีและความคิดที่ไม่ดีรอบตัว

(โดย "ประกาศ" ฉันหมายถึงให้ต้นแบบฟังก์ชั่นที่ไม่มีร่างกายโดย "กำหนด" ฉันหมายถึงให้รหัสที่แท้จริงของร่างกายฟังก์ชั่น; นี่คือคำศัพท์มาตรฐาน C)


2
มันไม่ได้เป็น ความคิดที่ไม่ดี - สิ่งต่าง ๆ นี้สามารถพบได้ในส่วนหัวของ GNU Libc
SK-logic

แต่สิ่งที่เกี่ยวกับการห่อสำนวนของไฟล์ส่วนหัวในคำสั่งการรวบรวมเงื่อนไข? จากนั้นแม้จะมีฟังก์ชั่นที่ประกาศและกำหนดไว้ในส่วนหัวก็จะได้รับการโหลดเพียงครั้งเดียว ฉันใหม่สำหรับ C ดังนั้นฉันอาจเข้าใจผิด
user305964

2
@papiro ปัญหาคือการตัดคำนั้นปกป้องในระหว่างการคอมไพเลอร์ครั้งเดียวเท่านั้น ดังนั้นหาก foo.c ถูกคอมไพล์ไปยัง foo.o ในการรันครั้งเดียว bar.c to bar.o ในอีกอันหนึ่งและ foo.o และ bar.o จะถูกลิงค์ไปยัง a.out ในหนึ่งในสาม (ตามปกติ) การตัดคำนั้น ไม่ได้ป้องกันอินสแตนซ์หลายรายการโดยหนึ่งรายการในแต่ละอ็อบเจ็กต์ไฟล์
David Conrad

ปัญหาอธิบายไว้ที่นี่#ifndef HEADER_Hไม่ควรป้องกันอะไร
Robert Harvey

27

C และ C ++ มีพฤติกรรมเหมือนกันมากในเรื่องนี้ - คุณสามารถมีinlineฟังก์ชั่นในส่วนหัว ใน C ++, วิธีการใด ๆ inlineที่มีร่างกายที่อยู่ภายในกำหนดระดับคือโดยปริยาย ถ้าคุณต้องการที่จะทำเช่นเดียวกันใน C static inlineประกาศฟังก์ชั่น


" ประกาศฟังก์ชั่นstatic inline " ... และคุณจะยังคงมีฟังก์ชั่นหลายชุดในหน่วยการแปลแต่ละหน่วยที่ใช้งาน ใน C ++ ที่ไม่มีstatic inlineฟังก์ชั่นคุณจะมีเพียงหนึ่งสำเนา ในการมีการนำไปใช้จริงในส่วนหัวใน C คุณจะต้อง 1) ทำเครื่องหมายการนำไปใช้เป็นinline(เช่นinline void func(){do_something();}) และ 2) จริง ๆ แล้วบอกว่าฟังก์ชั่นนี้จะอยู่ในหน่วยการแปลเฉพาะบางหน่วย (เช่นvoid func();)
Ruslan

6

แนวคิดของไฟล์ส่วนหัวต้องการคำอธิบายเล็กน้อย:

ไม่ว่าคุณจะให้ไฟล์ในบรรทัดคำสั่งของคอมไพเลอร์หรือทำ '#include' คอมไพเลอร์ส่วนใหญ่ยอมรับไฟล์คำสั่งที่มีนามสกุล c, c, cpp, c ++ เป็นต้นเป็นไฟล์ต้นฉบับ อย่างไรก็ตามพวกเขามักจะมีตัวเลือกบรรทัดคำสั่งเพื่อให้สามารถใช้งานส่วนขยายใดก็ได้ไปยังไฟล์ต้นฉบับได้

โดยทั่วไปไฟล์ที่ให้ในบรรทัดคำสั่งเรียกว่า 'แหล่งที่มา' และไฟล์ที่รวมอยู่เรียกว่า 'ส่วนหัว'

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

ดังนั้นสำหรับแต่ละไฟล์ที่ให้ไว้ในบรรทัดคำสั่งคอมไพเลอร์ไฟล์ขนาดใหญ่จะถูกกำหนดให้คอมไพเลอร์ อาจมีรหัส / ข้อมูลซึ่งจะใช้หน่วยความจำและ / หรือสร้างสัญลักษณ์ที่จะอ้างอิงจากไฟล์อื่น ตอนนี้สิ่งเหล่านี้จะสร้างภาพ 'วัตถุ' ตัวลิงก์สามารถให้ 'สัญลักษณ์ที่ซ้ำกัน' หากพบสัญลักษณ์เดียวกันในไฟล์วัตถุมากกว่าสองไฟล์ที่เชื่อมโยงเข้าด้วยกัน บางทีนี่อาจเป็นเหตุผล; ไม่แนะนำให้ใส่รหัสในไฟล์ส่วนหัวซึ่งสามารถสร้างสัญลักษณ์ในไฟล์วัตถุ

'อินไลน์' มักจะมีการ inline .. แต่เมื่อการดีบักพวกเขาอาจไม่ได้ inline ดังนั้นตัวเชื่อมโยงจึงไม่ให้ข้อผิดพลาดที่กำหนดทวีคูณ ง่าย ... นี่คือสัญลักษณ์ 'อ่อนแอ' และตราบใดที่ข้อมูล / รหัสสำหรับสัญลักษณ์ที่อ่อนแอจากวัตถุทั้งหมดมีขนาดและเนื้อหาเท่ากันลิงก์ที่เชื่อมโยงจะเก็บสำเนาหนึ่งชุดและคัดลอกจากวัตถุอื่น มันได้ผล.


3

คุณสามารถทำสิ่งนี้ได้ใน C99: inlineรับประกันว่าจะมีการจัดเตรียมฟังก์ชั่นไว้ที่อื่นดังนั้นหากฟังก์ชั่นไม่ได้ถูก inline คำจำกัดความของมันจะถูกแปลเป็นประกาศ (เช่นการนำไปปฏิบัติถูกทิ้ง) staticและแน่นอนคุณสามารถใช้


1

คำพูดมาตรฐาน 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" สัญลักษณ์เป็นสัญลักษณ์ที่อ่อนแอซึ่งไม่ได้ติดแท็กเป็นสัญลักษณ์ของวัตถุที่อ่อนแอโดยเฉพาะ เมื่อสัญลักษณ์ที่อ่อนแอถูกเชื่อมโยงกับสัญลักษณ์ที่กำหนดปกติจะใช้สัญลักษณ์ที่กำหนดปกติโดยไม่มีข้อผิดพลาด เมื่อสัญลักษณ์ที่ไม่ได้กำหนดอ่อนเชื่อมโยงและไม่มีการกำหนดสัญลักษณ์สัญลักษณ์ของค่าจะถูกกำหนดในลักษณะเฉพาะของระบบโดยไม่มีข้อผิดพลาด ในบางระบบตัวพิมพ์ใหญ่บ่งชี้ว่ามีการระบุค่าเริ่มต้น


-4

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

พวกเขาอาจมีลักษณะคล้ายกันกับวงเล็บปีกกาและคำหลักเดียวกันจำนวนมาก แต่พวกเขาเป็นภาษาที่แตกต่างกัน


1
ไม่จริง ๆ มีคำตอบจริง ๆ และหนึ่งในเป้าหมายหลักของ C ++ ก็คือการย้อนกลับเข้ากันได้กับ C.
Ed S.

4
ไม่มันถูกออกแบบมาให้มี "ความเข้ากันได้ระดับสูง C" และ "ไม่มีความเข้ากันไม่ได้กับ C" (ทั้งจาก Stroustrup) ฉันยอมรับว่าสามารถให้คำตอบในเชิงลึกได้มากขึ้นเพื่อเน้นว่าทำไมความไม่เข้ากันโดยเฉพาะอย่างยิ่งนี้จึงไม่ฟรี รู้สึกอิสระที่จะจัดหาหนึ่ง
Paul Butcher

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