"อินไลน์" ที่ไม่มี "คงที่" หรือ "ภายนอก" จะมีประโยชน์ใน C99 หรือไม่?


97

เมื่อฉันพยายามสร้างรหัสนี้

inline void f() {}

int main()
{
    f();
}

โดยใช้บรรทัดคำสั่ง

gcc -std=c99 -o a a.c

ฉันได้รับข้อผิดพลาดตัวเชื่อมโยง (ไม่ได้กำหนดอ้างอิงถึงf) ข้อผิดพลาดจะหายไปหากฉันใช้static inlineหรือextern inlineแทนที่จะเป็นเพียงแค่inlineหรือถ้าฉันคอมไพล์ด้วย-O(ดังนั้นฟังก์ชันจะถูกแทรกในบรรทัด)

พฤติกรรมนี้ดูเหมือนจะกำหนดไว้ในย่อหน้า 6.7.4 (6) ของมาตรฐาน C99:

หากการประกาศขอบเขตไฟล์ทั้งหมดสำหรับฟังก์ชันในหน่วยการแปลมีตัวinlineระบุฟังก์ชันที่ไม่มีexternคำจำกัดความในหน่วยการแปลนั้นจะเป็นนิยามแบบอินไลน์ คำจำกัดความแบบอินไลน์ไม่ได้ให้คำจำกัดความภายนอกสำหรับฟังก์ชันและไม่ห้ามการกำหนดภายนอกในหน่วยการแปลอื่น นิยามแบบอินไลน์เป็นทางเลือกให้กับนิยามภายนอกซึ่งนักแปลอาจใช้เพื่อเรียกใช้ฟังก์ชันใด ๆ ในหน่วยการแปลเดียวกัน ไม่ได้ระบุว่าการเรียกใช้ฟังก์ชันใช้นิยามอินไลน์หรือนิยามภายนอก

ถ้าฉันเข้าใจทั้งหมดนี้อย่างถูกต้องหน่วยคอมไพล์ที่มีฟังก์ชันที่กำหนดไว้inlineในตัวอย่างด้านบนจะคอมไพล์อย่างสม่ำเสมอก็ต่อเมื่อมีฟังก์ชันภายนอกที่มีชื่อเดียวกันและฉันไม่เคยรู้ว่าฟังก์ชันของตัวเองหรือฟังก์ชันภายนอกถูกเรียกใช้

พฤติกรรมนี้ไม่โง่อย่างสมบูรณ์หรือไม่? การกำหนดฟังก์ชันinlineโดยไม่มีstaticหรือexternใน C99 มีประโยชน์หรือไม่? ฉันพลาดอะไรไปรึเปล่า?

สรุปคำตอบ

แน่นอนว่าฉันพลาดอะไรบางอย่างไปและพฤติกรรมก็ไม่ได้บ้าคลั่ง :)

ตามที่Nemo อธิบายแนวคิดคือการกำหนดนิยามของฟังก์ชัน

inline void f() {}

ในไฟล์ส่วนหัวและมีเพียงการประกาศเท่านั้น

extern inline void f();

ในไฟล์. c ที่เกี่ยวข้อง เฉพาะการexternประกาศเท่านั้นที่ทริกเกอร์การสร้างรหัสไบนารีที่มองเห็นได้ภายนอก และไม่มีการใช้งานinlineในไฟล์. c แต่มีประโยชน์เฉพาะในส่วนหัวเท่านั้น

ในฐานะที่เป็นเหตุผลของคณะกรรมการ C99 ที่ยกมาในโจนาธานตอบ explicates, inlineเป็นข้อมูลเกี่ยวกับ optimisations คอมไพเลอร์ที่ต้องใช้ความหมายของฟังก์ชั่นที่จะสามารถมองเห็นได้ที่เว็บไซต์ของการโทร สิ่งนี้สามารถทำได้โดยการใส่คำจำกัดความไว้ในส่วนหัวเท่านั้นและแน่นอนว่าคำจำกัดความในส่วนหัวจะต้องไม่ปล่อยรหัสทุกครั้งที่คอมไพเลอร์มองเห็น แต่เนื่องจากคอมไพลเลอร์ไม่ได้ถูกบังคับให้อินไลน์ฟังก์ชันจึงต้องมีนิยามภายนอกอยู่ที่ไหนสักแห่ง


borderline ซ้ำกันของstackoverflow.com/questions/5369888/…
Earlz


@Earlz: ขอบคุณสำหรับลิงค์ คำถามของฉันเกี่ยวกับเหตุผลที่อยู่เบื้องหลังย่อหน้าที่ยกมาของมาตรฐานและหากเคยมีกรณีการใช้งานที่inlineไม่มีstaticและexternแม้ว่า น่าเสียดายที่คำถามนั้นไม่ครอบคลุมประเด็นเหล่านี้
Sven Marnach

@Nemo: ฉันอ่านคำถามนั้นและคำตอบก่อนที่จะโพสต์ของฉัน อีกครั้งฉันไม่ได้ถามว่า "มันทำงานอย่างไร" แต่เป็น "แนวคิดเบื้องหลังพฤติกรรมนี้" คืออะไร? ฉันค่อนข้างมั่นใจว่าฉันพลาดอะไรบางอย่างที่นี่
Sven Marnach

1
ใช่นั่นคือเหตุผลที่ฉันพูดว่า "เส้นเขตแดน" โดยส่วนตัวฉันคิดว่านี่เป็นการถามคำถามที่แตกต่างอย่างสิ้นเชิง
Earlz

คำตอบ:


40

อันที่จริงคำตอบที่ยอดเยี่ยมนี้ยังตอบคำถามของคุณด้วยฉันคิดว่า:

extern inline ทำอะไร?

แนวคิดคือสามารถใช้ "inline" ในไฟล์ส่วนหัวจากนั้น "extern inline" ในไฟล์. c "extern inline" เป็นเพียงวิธีที่คุณสั่งคอมไพเลอร์ว่าไฟล์อ็อบเจ็กต์ใดควรมีโค้ดที่สร้างขึ้น (มองเห็นได้จากภายนอก)

[ปรับปรุงเพื่ออธิบาย]

ฉันไม่คิดว่าจะมีการใช้งาน "inline" (โดยไม่มี "static" หรือ "extern") ในไฟล์. c แต่ในไฟล์ส่วนหัวนั้นสมเหตุสมผลและต้องมีการประกาศ "extern inline" ที่เกี่ยวข้องในไฟล์. c บางไฟล์เพื่อสร้างโค้ดแบบสแตนด์อะโลน


1
ขอบคุณฉันเริ่มได้แนวคิดแล้ว!
Sven Marnach

8
ใช่ฉันไม่เคยเข้าใจตัวเองจนถึงตอนนี้ขอบคุณที่ถาม :-) เป็นเรื่องแปลกที่คำจำกัดความ "อินไลน์" ที่ไม่ใช่ภายนอกจะอยู่ในส่วนหัว (แต่ไม่จำเป็นต้องส่งผลให้เกิดการสร้างโค้ดใด ๆ เลย) ในขณะที่การประกาศ "อินไลน์ภายนอก" จะอยู่ในไฟล์. c และทำให้โค้ดเป็น ถูกสร้างขึ้น
Nemo

3
หมายความว่าฉันเขียนinline void f() {}ในส่วนหัวและextern inline void f();ในไฟล์. c หรือไม่? ดังนั้นนิยามฟังก์ชันที่แท้จริงจะอยู่ในส่วนหัวและไฟล์. c มีเพียงการประกาศในกรณีนี้ในการกลับคำสั่งปกติ?
Sven Marnach

1
@endolith: ฉันไม่เข้าใจคำถามของคุณ เราจะคุยinline โดยไม่ต้อง หรือstatic externแน่นอนว่าstatic inlineดี แต่นั่นไม่ใช่สิ่งที่คำถามและคำตอบนี้เกี่ยวกับ
Nemo

2
@MatthieuMoy คุณต้องใช้-std=c99แทน-std=gnu89.
a3f

27

จากมาตรฐาน (ISO / IEC 9899: 1999) เอง:

ภาคผนวก J.2 พฤติกรรมที่ไม่ได้กำหนด

  • ...
  • ฟังก์ชันที่มีการเชื่อมโยงภายนอกถูกประกาศด้วยตัวinlineระบุฟังก์ชัน แต่ไม่ได้กำหนดไว้ในหน่วยการแปลเดียวกัน (6.7.4)
  • ...

คณะกรรมการ C99 เขียนเหตุผลและกล่าวว่า:

6.7.4 ตัวระบุฟังก์ชัน

คุณลักษณะใหม่ของ C99:inlineคำหลักที่ดัดแปลงมาจากภาษา C ++ เป็นฟังก์ชั่นระบุว่าสามารถนำมาใช้เฉพาะในการประกาศฟังก์ชั่น มีประโยชน์สำหรับการปรับแต่งโปรแกรมที่ต้องการความหมายของฟังก์ชันเพื่อให้มองเห็นได้ที่ไซต์ของการโทร (โปรดทราบว่า Standard ไม่ได้พยายามระบุลักษณะของการเพิ่มประสิทธิภาพเหล่านี้)

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

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

โปรแกรมสามารถมีหน่วยการแปลที่มีนิยามภายนอกหน่วยการแปลที่มีนิยามแบบอินไลน์และหน่วยการแปลที่มีการประกาศ แต่ไม่มีคำจำกัดความสำหรับฟังก์ชัน การเรียกในหน่วยการแปลหลังจะใช้นิยามภายนอกตามปกติ

นิยามแบบอินไลน์ของฟังก์ชันถือเป็นคำจำกัดความที่แตกต่างจากนิยามภายนอก หากการเรียกใช้ฟังก์ชันบางอย่างที่funcมีการเชื่อมโยงภายนอกเกิดขึ้นโดยที่สามารถมองเห็นนิยามแบบอินไลน์ได้ลักษณะการทำงานจะเหมือนกับการเรียกไปยังฟังก์ชันอื่นกล่าว __funcโดยมีการเชื่อมโยงภายใน โปรแกรมที่สอดคล้องต้องไม่ขึ้นอยู่กับฟังก์ชันที่เรียกใช้ นี่คือโมเดลอินไลน์ใน Standard

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

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

เนื่องจากการนำไปใช้งานอาจใช้นิยามแบบอินไลน์สำหรับหนึ่งในการเรียกsaddrและใช้นิยามภายนอกสำหรับอีกรายการหนึ่งการดำเนินการความเท่าเทียมกันจึงไม่รับประกันว่าจะประเมินเป็น 1 (จริง) สิ่งนี้แสดงให้เห็นว่าออบเจ็กต์แบบคงที่ที่กำหนดภายในนิยามอินไลน์นั้นแตกต่างจากอ็อบเจ็กต์ที่เกี่ยวข้องในนิยามภายนอก สิ่งนี้กระตุ้นให้เกิดข้อ จำกัด ในการกำหนดสิ่งที่ไม่ใช่constวัตถุประเภทนี้

Inlining ถูกเพิ่มเข้าไปใน Standard เพื่อให้สามารถนำไปใช้กับเทคโนโลยี linker ที่มีอยู่ได้และส่วนย่อยของ C99 inlining เข้ากันได้กับ C ++ สิ่งนี้ทำได้โดยกำหนดให้หน่วยการแปลหนึ่งหน่วยที่มีนิยามของฟังก์ชันอินไลน์ถูกระบุเป็นหน่วยที่ให้คำจำกัดความภายนอกสำหรับฟังก์ชัน เนื่องจากข้อกำหนดดังกล่าวประกอบด้วยการประกาศที่ไม่มีinlineคีย์เวิร์ดหรือมีทั้งสองอย่างinlineและตัวexternแปล C ++ จะยอมรับด้วย

Inlining ใน C99 จะขยายข้อกำหนด C ++ ได้สองวิธี ขั้นแรกหากมีการประกาศฟังก์ชัน inlineในหน่วยการแปลหนึ่งหน่วยไม่จำเป็นต้องประกาศinlineในหน่วยการแปลอื่น ๆ สิ่งนี้ช่วยให้ตัวอย่างเช่นฟังก์ชันไลบรารีที่จะอินไลน์ภายในไลบรารี แต่สามารถใช้ได้ผ่านนิยามภายนอกที่อื่นเท่านั้น ทางเลือกในการใช้ฟังก์ชัน wrapper สำหรับฟังก์ชันภายนอกต้องใช้ชื่อเพิ่มเติม และอาจส่งผลเสียต่อประสิทธิภาพการทำงานหากนักแปลไม่ได้ทำการแทนที่แบบอินไลน์

ประการที่สองข้อกำหนดที่ว่าคำจำกัดความทั้งหมดของฟังก์ชันอินไลน์เป็น "เหมือนกันทุกประการ" จะถูกแทนที่ด้วยข้อกำหนดที่ว่าลักษณะการทำงานของโปรแกรมไม่ควรขึ้นอยู่กับว่ามีการเรียกใช้ด้วยคำจำกัดความแบบอินไลน์ที่มองเห็นได้หรือคำจำกัดความภายนอกของ a ฟังก์ชัน สิ่งนี้ช่วยให้คำจำกัดความแบบอินไลน์มีความเชี่ยวชาญสำหรับการใช้งานภายในหน่วยการแปลเฉพาะ ตัวอย่างเช่นนิยามภายนอกของฟังก์ชันไลบรารีอาจรวมถึงการตรวจสอบอาร์กิวเมนต์บางอย่างที่ไม่จำเป็นสำหรับการเรียกที่ทำจากฟังก์ชันอื่นในไลบรารีเดียวกัน ส่วนขยายเหล่านี้มีข้อดีบางประการ และโปรแกรมเมอร์ที่กังวลเกี่ยวกับความเข้ากันได้ก็สามารถปฏิบัติตามกฎ C ++ ที่เข้มงวดขึ้นได้

โปรดทราบว่าไม่เหมาะสมสำหรับการนำไปใช้ในการจัดเตรียมคำจำกัดความแบบอินไลน์ของฟังก์ชันไลบรารีมาตรฐานในส่วนหัวมาตรฐานเนื่องจากอาจทำลายโค้ดดั้งเดิมบางส่วนที่ประกาศฟังก์ชันไลบรารีมาตรฐานใหม่หลังจากรวมส่วนหัว inlineคำหลักที่มีจุดมุ่งหมายเพียงเพื่อให้ผู้ใช้มีวิธีแบบพกพาที่จะแนะนำ inlining ของฟังก์ชั่น เนื่องจากส่วนหัวมาตรฐานไม่จำเป็นต้องพกพาได้การใช้งานจึงมีตัวเลือกอื่น ๆ ตามบรรทัดของ:

#define abs(x) __builtin_abs(x)

หรือกลไกอื่น ๆ ที่ไม่สามารถพกพาได้สำหรับการฝังฟังก์ชันไลบรารีมาตรฐาน


ขอบคุณข้อมูลนี้มีรายละเอียดมาก - และอ่านได้ง่ายพอ ๆ กับมาตรฐาน :-) ฉันรู้ว่าฉันต้องขาดอะไรบางอย่าง คุณช่วยอ้างอิงได้ไหมว่าคุณได้สิ่งนี้มาจากไหน
Sven Marnach

ฉันคิดว่าคุณสามารถใช้ Google เพื่อค้นหาลิงก์: open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
Sven Marnach

@Sven: ฉันยืม URL จากการค้นหาของคุณและใส่ไว้ในคำตอบ เอกสารที่ฉันใช้เป็นสำเนาของเหตุผลที่ฉันบันทึกไว้ก่อนหน้านี้ (ในปี 2548) แต่เป็น V5.10 ด้วย
Jonathan Leffler

4
ขอบคุณอีกครั้ง. ความเข้าใจที่มีค่าที่สุดที่ฉันได้รับจากคำตอบคือมีเอกสารที่มีเหตุผลของมาตรฐาน C99 (และอาจมีเอกสารสำหรับมาตรฐานอื่น ๆ ด้วย) ในขณะที่คำตอบของ Nemo ให้ปลาฉันคนนี้ก็สอนฉันให้ตกปลา
Sven Marnach

0

> ฉันได้รับข้อผิดพลาดตัวเชื่อมโยง (ไม่ได้กำหนดอ้างอิงถึงf)

ทำงานที่นี่: Linux x86-64, GCC 4.1.2 อาจเป็นจุดบกพร่องในคอมไพเลอร์ของคุณ ฉันไม่เห็นอะไรเลยในย่อหน้าที่อ้างถึงจากมาตรฐานที่ห้ามโปรแกรมที่กำหนด หมายเหตุใช้ถ้ามากกว่าIFF

นิยามแบบอินไลน์เป็นทางเลือกให้กับนิยามภายนอกซึ่งนักแปลอาจใช้เพื่อเรียกใช้ฟังก์ชันใด ๆ ในหน่วยการแปลเดียวกัน

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


2
มาตรฐานระบุว่าคอมไพลเลอร์อาจสมมติว่ามีฟังก์ชันภายนอกที่มีชื่อเดียวกันและเรียกมันแทนเวอร์ชันอินไลน์ดังนั้นฉันไม่คิดว่ามันเป็นบั๊กของคอมไพเลอร์ ฉันลองใช้ gcc 4.3, 4.4 และ 4.5 ​​ทั้งหมดให้ข้อผิดพลาดตัวเชื่อมโยง
Sven Marnach
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.