วิธีการใช้คำหลัก extern ใน C อย่างถูกต้อง


235

คำถามของฉันเป็นเรื่องเกี่ยวกับเมื่อฟังก์ชันควรจะอ้างอิงกับexternคำหลักใน C.

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

ฉันอาจจะคิดเกี่ยวกับวิธีการexternทำงานที่ไม่ถูกต้องและถ้าเป็นเช่นนั้นโปรดแก้ไขฉัน

แก้ไข:คุณควรexternบางสิ่งบางอย่างเมื่อมันเป็นการประกาศเริ่มต้นโดยไม่มีคำหลักในไฟล์ส่วนหัว?


ที่เกี่ยวข้องกับฟังก์ชั่น: stackoverflow.com/questions/856636/…สำหรับ varables: stackoverflow.com/questions/1433204
Ciro Santilli 郝海东冠状冠状病六四事件法轮功

คำตอบ:


290

" extern" เปลี่ยนการเชื่อมโยง ด้วยคีย์เวิร์ดฟังก์ชัน / ตัวแปรจะถูกใช้งานที่อื่นและการแก้ไขจะถูกเลื่อนไปยังลิงเกอร์

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


1
เหตุใดจึงมีสิ่งภายนอกเดียวกันใน Git: ซอฟต์แวร์ที่ได้รับความนิยมและทันสมัยตรวจสอบ: github.com/git/git/blob/master/strbuf.h
rsjethani

K&R ไม่ทราบว่าเป็นค่าเริ่มต้นที่จะประกาศฟังก์ชั่นเป็น "extern" แต่คำตอบนี้แก้ความสับสนของฉัน!
acgtyrant

@rsjethani ฉันคิดว่ามันจะทำให้เอกสารเข้มงวดมากขึ้นและจัดรูปแบบ
acgtyrant

อาจเป็นคำถามที่โง่เง่า แต่สิ่งนี้เปรียบเทียบกับการประกาศไปข้างหน้าได้อย่างไร
weberc2

196

externบอกคอมไพเลอร์ว่าข้อมูลนี้ถูกกำหนดไว้ที่ไหนสักแห่งและจะเชื่อมต่อกับ linker

ด้วยความช่วยเหลือของการตอบสนองที่นี่และพูดคุยกับเพื่อนไม่กี่ที่นี่เป็นตัวอย่างในทางปฏิบัติของการใช้งานของextern

ตัวอย่างที่ 1 -เพื่อแสดงหลุมพราง:

File stdio.h:

int errno;
/* other stuff...*/

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

หาก myCFile1.o และ myCFile2.o มีการเชื่อมโยงแต่ละไฟล์คมีสำเนาที่แยกต่างหากจากerrno ปัญหานี้เป็นปัญหาเนื่องจากerrnoเดียวกันนั้นควรมีอยู่ในไฟล์ที่เชื่อมโยงทั้งหมด

ตัวอย่างที่ 2 -การแก้ไข

File stdio.h:

extern int errno;
/* other stuff...*/

File stdio.c

int errno;

myCFile1.c:
#include <stdio.h>

Code...

myCFile2.c:
#include <stdio.h>

Code...

ตอนนี้ถ้าทั้ง myCFile1.o และ MyCFile2.o มีการเชื่อมโยงโดยลิงเกอร์พวกเขาจะจุดทั้งเดียวกันerrno ดังนั้นการแก้ปัญหาการดำเนินงานที่มีextern


70
ปัญหาไม่ได้อยู่ที่โมดูล myCFile1 และ myCFile2 มีสำเนาของ errno แยกต่างหากก็คือพวกเขาทั้งคู่มีการเปิดเผยสัญลักษณ์ที่เรียกว่า "errno" เมื่อลิงเกอร์เห็นสิ่งนี้มันจะไม่รู้ว่าจะเลือก "errno" ใดดังนั้นมันจะประกันตัวด้วยข้อความแสดงข้อผิดพลาด
cwick

2
"ลิงก์โดยลิงเกอร์" หมายความว่าอย่างไร ทุกคนใช้คำนี้ฉันไม่พบคำจำกัดความใด ๆ :(
Marcel Falliere

7
@MarcelFalliere Wiki ~ คอมไพเลอร์รวบรวมไฟล์ต้นฉบับแต่ละไฟล์ด้วยตนเองและสร้างไฟล์วัตถุสำหรับไฟล์ต้นฉบับแต่ละไฟล์ ตัวเชื่อมโยงเชื่อมโยงไฟล์วัตถุเหล่านี้กับ 1 ที่ปฏิบัติการได้
Bitterblue

1
GCC @cwick จะไม่ให้ข้อผิดพลาดหรือคำเตือนแม้หลังการใช้และ-Wall -pedanticทำไม แล้วยังไง ?
b-ak

6
ยามรักษาการณ์รวมไม่ได้ป้องกันสิ่งที่แน่นอนนี้หรือไม่
obskyr

32

มันได้รับการระบุว่าexternคำหลักที่ซ้ำซ้อนสำหรับฟังก์ชั่น

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


@ aib "ฟังก์ชั่นซ้ำซ้อน" ตรวจสอบความคิดเห็นของฉันในคำตอบของ Bluebrother
rsjethani

ถ้าคุณไม่ต้องการแสดงฟังก์ชั่นใด ๆ ในไฟล์ส่วนหัวล่ะ มันจะเป็นการดีกว่าถ้าจะประกาศตัวแปรในไฟล์ C หนึ่งไฟล์และเข้าถึงมันโดย extern ในอีกไฟล์หนึ่ง ปล่อยให้ลิงเกอร์แก้ปัญหาและซ่อนส่วนที่เหลือ
ste3e

16

หลายปีต่อมาฉันค้นพบคำถามนี้ หลังจากอ่านคำตอบและความคิดเห็นทุกครั้งฉันคิดว่าฉันสามารถอธิบายรายละเอียดบางอย่างได้ ... นี่อาจเป็นประโยชน์สำหรับผู้ที่มาที่นี่ผ่านการค้นหาของ Google

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

ลองกำหนด 3 ฟังก์ชันต้นแบบ:

//--------------------------------------
//Filename: "my_project.H"
extern int function_1(void);
static int function_2(void);
       int function_3(void);

ไฟล์ส่วนหัวสามารถใช้งานได้โดยซอร์สโค้ดหลักดังนี้:

//--------------------------------------
//Filename: "my_project.C"
#include "my_project.H"

void main(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 1234;

ในการรวบรวมและลิงก์เราต้องกำหนด "function_2" ในไฟล์ซอร์สโค้ดเดียวกับที่เราเรียกใช้ฟังก์ชันนั้น สามารถกำหนดฟังก์ชันอื่นอีกสองฟังก์ชันในซอร์สโค้ด " .C" ที่แตกต่างกันหรืออาจอยู่ในไฟล์ไบนารี่ใด ๆ ( .OBJ, * .LIB, * .DLL) ซึ่งเราอาจไม่มีซอร์สโค้ด

ลองรวมส่วนหัว "my_project.H" อีกครั้งในไฟล์ "* .C" อื่นเพื่อเข้าใจความแตกต่างได้ดียิ่งขึ้น ในโครงการเดียวกันเราเพิ่มไฟล์ต่อไปนี้:

//--------------------------------------
//Filename: "my_big_project_splitted.C"
#include "my_project.H"

void old_main_test(void){
    int v1 = function_1();
    int v2 = function_2();
    int v3 = function_3();
}

int function_2(void) return 5678;

int function_1(void) return 12;
int function_3(void) return 34;

คุณสมบัติสำคัญที่ควรแจ้งให้ทราบ:

  • เมื่อฟังก์ชั่นถูกกำหนดเป็น "คงที่" ในไฟล์ส่วนหัวคอมไพเลอร์ / ลิงเกอร์จะต้องค้นหาอินสแตนซ์ของฟังก์ชั่นที่มีชื่อในแต่ละโมดูลซึ่งใช้ที่มีไฟล์

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

  • ตัวระบุ "extern" ไม่จำเป็นสำหรับฟังก์ชั่น เมื่อไม่พบ "คงที่" ฟังก์ชันจะถือว่าเป็น "ภายนอก" เสมอ

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

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


ดังนั้นทำไมฟังก์ชั่นคงที่จึงต้องการคำจำกัดความเมื่อเทียบกับฟังก์ชั่นภายนอก? (ฉันรู้ว่านี่มันสายไปแล้ว 2 ปี แต่จริงๆแล้วมันมีประโยชน์สำหรับการทำความเข้าใจจริงๆ)
SubLock69

2
จำเป็นต้องใช้คำจำกัดความถ้าคุณเรียกใช้ฟังก์ชันที่บรรทัด 100 และติดตั้งที่บรรทัด 500 บรรทัด 100 จะประกาศต้นแบบที่ไม่ได้กำหนด ดังนั้นคุณเพิ่มต้นแบบใกล้ด้านบน
Christian Gingras

15

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

(หากจำเป็นต้องมีการเชื่อมโยงแบบคงที่ฟังก์ชันจะต้องประกาศเป็น 'คงที่' ทั้งในต้นแบบและส่วนหัวของฟังก์ชั่นและโดยปกติทั้งสองควรอยู่ในไฟล์. c เดียวกัน)


8

บทความที่ดีมากที่ฉันมาเกี่ยวกับexternคำหลักพร้อมด้วยตัวอย่าง: http://www.geeksforgeeks.org/understanding-extern-keyword-in-c/

แม้ว่าฉันจะไม่เห็นด้วยว่าการใช้งานexternในการประกาศฟังก์ชันซ้ำซ้อน นี่ควรจะเป็นการตั้งค่าคอมไพเลอร์ ดังนั้นฉันขอแนะนำให้ใช้externในการประกาศฟังก์ชั่นเมื่อมีความจำเป็น


3
ฉันอ่านบทความ geeksforgeeks.org ก่อนฉันมาที่นี่ แต่พบว่าเขียนค่อนข้างแย่ นอกเหนือจากข้อบกพร่องทางไวยากรณ์และไวยากรณ์มันใช้คำจำนวนมากเพื่อให้จุดเดียวกันหลายครั้งแล้ว skims ข้อมูลที่สำคัญ ตัวอย่างเช่นในตัวอย่างที่ 4 ทันใดนั้นก็มี 'somefile.h' รวมอยู่ด้วย แต่ไม่มีอะไรพูดถึงมันนอกจาก: "หากว่า somefile.h มีคำจำกัดความของ var" ข้อมูลที่เรา "คาดการณ์" ก็เป็นข้อมูลที่ฉันต้องการ น่าเสียดายที่ไม่มีคำตอบในหน้านี้ดีกว่านี้มาก
Elise van Looij

6

externถ้าแต่ละไฟล์ในโปรแกรมของคุณจะรวบรวมครั้งแรกไปยังแฟ้มวัตถุแล้วไฟล์วัตถุที่มีการเชื่อมโยงกันที่คุณต้องการ มันบอกคอมไพเลอร์ "ฟังก์ชั่นนี้มีอยู่ แต่รหัสของมันอยู่ที่อื่นอย่าตกใจ"


อืม, นั่นคือวิธีการแปลที่ทำได้ตามปกติ: ซอร์สไฟล์คอมไพล์กับไฟล์ออบเจ็กต์, และเชื่อมโยงกันแล้ว เมื่อใดที่คุณไม่ต้องการใช้ภายนอกในกรณีนี้ คุณไม่สามารถใช้ #include เพื่อรับฟังก์ชั่นได้ แต่ควรใช้ฟังก์ชั่นต้นแบบ ฉันไม่เข้าใจสิ่งที่คุณกำลังพูดถึง
David Thornley

ฉันดูเหมือนจะมีปัญหานี้เมื่อเร็ว ๆ นี้ของการเข้าใจผิดสิ่ง ขอโทษด้วยกับเรื่องนั้น. เมื่อฉันยังใหม่กับ C ฉันจะ #include "file.c" เพื่อรวมฟังก์ชั่นในไฟล์เดียวลงในไฟล์อื่นโดยตรง จากนั้นฉันก็หาวิธีใช้ 'ภายนอก' ฉันคิดว่าเขาทำผิดพลาดเหมือนฉัน
คริสลัทซ์

4

externประกาศทั้งหมดของฟังก์ชันและตัวแปรในส่วนหัวของไฟล์ที่ควรจะเป็น

ยกเว้นกฎนี้มีฟังก์ชั่นแบบอินไลน์ที่กำหนดไว้ในส่วนหัวและตัวแปรที่ - แม้ว่าที่กำหนดไว้ในส่วนหัว - จะต้องท้องถิ่นไปยังหน่วยการแปล (แฟ้มแหล่งที่มาส่วนหัวที่ได้รับการรวมอยู่ใน): staticเหล่านี้ควรจะ

ในไฟล์ต้นฉบับexternไม่ควรใช้สำหรับฟังก์ชั่นและตัวแปรที่กำหนดไว้ในไฟล์ เพียงนำคำจำกัดความท้องถิ่นด้วยstaticและไม่ทำอะไรเลยสำหรับคำจำกัดความที่ใช้ร่วมกันพวกเขาจะเป็นสัญลักษณ์ภายนอกโดยค่าเริ่มต้น

เหตุผลเพียงอย่างเดียวที่ใช้externในไฟล์ต้นฉบับคือการประกาศฟังก์ชั่นและตัวแปรที่กำหนดไว้ในไฟล์ต้นฉบับอื่นและไม่มีไฟล์ส่วนหัวให้


การประกาศฟังก์ชันต้นแบบexternนั้นไม่จำเป็นจริงๆ บางคนไม่ชอบเพราะจะเสียพื้นที่และการประกาศฟังก์ชั่นมีแนวโน้มที่จะเกินขีด จำกัด บรรทัด คนอื่นชอบเพราะวิธีนี้ฟังก์ชั่นและตัวแปรสามารถได้รับการปฏิบัติในลักษณะเดียวกัน


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

@ เลน: externเป็นตัวเลือกสำหรับการประกาศฟังก์ชั่น แต่ฉันชอบที่จะปฏิบัติต่อตัวแปรและฟังก์ชั่นในลักษณะเดียวกัน - อย่างน้อยนั่นก็เป็นสิ่งที่สมเหตุสมผลที่สุดที่ฉันจะได้รับเพราะฉันจำไม่ได้ว่าทำไมฉันถึงเริ่มทำสิ่งนี้;)
Christoph

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

3

ฟังก์ชั่นที่กำหนดไว้จริงในไฟล์ต้นฉบับอื่น ๆ ควรจะประกาศในส่วนหัวเท่านั้น ในกรณีนี้คุณควรใช้externเมื่อประกาศต้นแบบในส่วนหัว

ส่วนใหญ่แล้วฟังก์ชั่นของคุณจะเป็นหนึ่งในสิ่งต่อไปนี้ (คล้ายกับแนวปฏิบัติที่ดีที่สุด):

  • คงที่ (ฟังก์ชั่นปกติที่มองไม่เห็นนอกไฟล์. c)
  • แบบคงที่แบบอินไลน์ (inlines จากไฟล์. c หรือ. h)
  • extern (ประกาศในส่วนหัวของประเภทถัดไป (ดูด้านล่าง))
  • [ไม่มีคำหลักใด ๆ ] (ฟังก์ชั่นปกติหมายถึงการเข้าถึงได้โดยใช้การประกาศภายนอก)

ทำไมคุณถึงต้องออกนอกลู่นอกทางเมื่อประกาศต้นแบบหากนี่เป็นค่าเริ่มต้น
lillq

@ เลน: อาจจะลำเอียงเล็กน้อย แต่ทุกโครงการมีสติที่ฉันได้ใช้ในการประชุมดังต่อไปนี้: ในส่วนหัวประกาศต้นแบบสำหรับฟังก์ชั่นภายนอกเท่านั้น (ภายนอกจึง) ในไฟล์. c สามารถใช้ต้นแบบต้นแบบธรรมดาเพื่อตัดความต้องการการสั่งซื้อเฉพาะ แต่ไม่ควรวางไว้ในส่วนหัว
Eduard - Gabriel Munteanu

1

เมื่อคุณมีฟังก์ชั่นที่กำหนดไว้ใน dll หรือ lib ที่แตกต่างกันเพื่อให้คอมไพเลอร์ defers เพื่อ linker เพื่อค้นหา กรณีทั่วไปคือเมื่อคุณเรียกใช้ฟังก์ชันจาก OS API

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.