ฉันรู้ว่าตัวแปรทั่วโลกใน C บางครั้งมีextern
คำหลัก คืออะไรextern
ตัวแปร? การประกาศเป็นอย่างไร ขอบเขตคืออะไร
สิ่งนี้เกี่ยวข้องกับการแชร์ตัวแปรข้ามไฟล์ต้นฉบับ แต่มันทำงานอย่างไรอย่างแม่นยำ? ฉันจะใช้extern
ที่ไหน
ฉันรู้ว่าตัวแปรทั่วโลกใน C บางครั้งมีextern
คำหลัก คืออะไรextern
ตัวแปร? การประกาศเป็นอย่างไร ขอบเขตคืออะไร
สิ่งนี้เกี่ยวข้องกับการแชร์ตัวแปรข้ามไฟล์ต้นฉบับ แต่มันทำงานอย่างไรอย่างแม่นยำ? ฉันจะใช้extern
ที่ไหน
คำตอบ:
ใช้extern
เป็นเพียงความเกี่ยวข้องเมื่อโปรแกรมที่คุณกำลังอาคารประกอบด้วยไฟล์หลายแหล่งที่มาเชื่อมโยงกันที่บางส่วนของตัวแปรที่กำหนดไว้สำหรับตัวอย่างเช่นในแฟ้มแหล่งที่มาfile1.c
จะต้องมีการอ้างอิงในแฟ้มแหล่งอื่น ๆ file2.c
เช่น
มันเป็นสิ่งสำคัญที่จะเข้าใจความแตกต่างระหว่างการกำหนดตัวแปรและการประกาศตัวแปร :
ตัวแปรถูกประกาศเมื่อคอมไพเลอร์ได้รับแจ้งว่ามีตัวแปรอยู่ (และนี่คือประเภทของมัน); มันไม่ได้จัดสรรที่เก็บข้อมูลสำหรับตัวแปร ณ จุดนั้น
ตัวแปรถูกกำหนดเมื่อคอมไพเลอร์จัดสรรหน่วยเก็บสำหรับตัวแปร
คุณอาจประกาศตัวแปรหลาย ๆ ครั้ง (แต่ครั้งเดียวก็เพียงพอแล้ว); คุณสามารถกำหนดได้เพียงครั้งเดียวภายในขอบเขตที่กำหนด นิยามตัวแปรยังเป็นการประกาศ แต่ไม่ใช่การประกาศตัวแปรทั้งหมดเป็นคำจำกัดความ
วิธีที่สะอาดและเชื่อถือได้ในการประกาศและกำหนดตัวแปรส่วนกลางคือการใช้ไฟล์ส่วนหัวเพื่อให้มีการextern
ประกาศตัวแปร
ส่วนหัวถูกรวมโดยไฟล์ต้นฉบับหนึ่งไฟล์ที่กำหนดตัวแปรและโดยไฟล์ต้นฉบับทั้งหมดที่อ้างอิงตัวแปร สำหรับแต่ละโปรแกรมไฟล์ต้นฉบับหนึ่งไฟล์ (และไฟล์เดียวเท่านั้น) จะกำหนดตัวแปร ในทำนองเดียวกันไฟล์ส่วนหัวหนึ่งไฟล์ (และไฟล์ส่วนหัวเดียวเท่านั้น) ควรประกาศตัวแปร ไฟล์ส่วนหัวมีความสำคัญ มันช่วยให้การตรวจสอบข้ามระหว่าง TU อิสระ (หน่วยการแปล - คิดว่าไฟล์ต้นฉบับ) และสร้างความมั่นคง
แม้ว่าจะมีวิธีอื่นในการทำวิธีนี้ง่ายและน่าเชื่อถือ มันคือการแสดงfile3.h
, file1.c
และfile2.c
:
extern int global_variable; /* Declaration of the variable */
#include "file3.h" /* Declaration made available here */
#include "prog1.h" /* Function declarations */
/* Variable defined here */
int global_variable = 37; /* Definition checked against declaration */
int increment(void) { return global_variable++; }
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
นั่นเป็นวิธีที่ดีที่สุดในการประกาศและกำหนดตัวแปรทั่วโลก
ไฟล์สองไฟล์ถัดไปทำให้สมบูรณ์สำหรับprog1
:
โปรแกรมที่สมบูรณ์แสดงการใช้งานฟังก์ชั่นดังนั้นการประกาศฟังก์ชั่นได้พุ่งเข้ามาทั้ง C99 และ C11 ต้องการฟังก์ชั่นที่จะประกาศหรือกำหนดก่อนที่จะใช้ (ในขณะที่ C90 ไม่ได้สำหรับเหตุผลที่ดี) ฉันใช้คำสำคัญที่extern
ด้านหน้าของการประกาศฟังก์ชั่นในส่วนหัวเพื่อความสอดคล้อง - เพื่อให้ตรงกับextern
ด้านหน้าของการประกาศตัวแปรในส่วนหัว หลายคนไม่ต้องการใช้extern
หน้าการประกาศฟังก์ชัน คอมไพเลอร์ไม่สนใจ - และในที่สุดฉันก็ไม่ได้ตราบใดที่คุณมีความสอดคล้องอย่างน้อยก็ในไฟล์ต้นฉบับ
extern void use_it(void);
extern int increment(void);
#include "file3.h"
#include "prog1.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog1
การใช้งานprog1.c
, file1.c
, file2.c
, และfile3.h
prog1.h
ไฟล์prog1.mk
นี้เป็น makefile สำหรับprog1
เท่านั้น มันจะทำงานร่วมกับรุ่นที่make
ผลิตส่วนใหญ่ตั้งแต่ประมาณเปลี่ยนสหัสวรรษ ไม่ได้เชื่อมโยงกับ GNU Make โดยเฉพาะ
# Minimal makefile for prog1
PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}
CC = gcc
SFLAGS = -std=c11
GFLAGS = -g
OFLAGS = -O3
WFLAG1 = -Wall
WFLAG2 = -Wextra
WFLAG3 = -Werror
WFLAG4 = -Wstrict-prototypes
WFLAG5 = -Wmissing-prototypes
WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS = # Set on command line only
CFLAGS = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS =
all: ${PROGRAM}
${PROGRAM}: ${FILES.o}
${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}
prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}
# If it exists, prog1.dSYM is a directory on macOS DEBRIS = a.out core *~ *.dSYM RM_FR = rm -fr
clean:
${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}
กฎจะถูกทำลายโดยผู้เชี่ยวชาญเท่านั้นและด้วยเหตุผลที่ดีเท่านั้น:
ไฟล์ส่วนหัวมีเพียงextern
การประกาศของตัวแปร - static
คำจำกัดความของตัวแปรไม่เคย
หรือไม่มีเงื่อนไข
สำหรับตัวแปรใด ๆ ที่กำหนดให้มีไฟล์ส่วนหัวเพียงหนึ่งไฟล์เท่านั้นที่ประกาศ (SPOT - Single Point of Truth)
ไฟล์ต้นฉบับไม่เคยมีextern
การประกาศของตัวแปร - ไฟล์ต้นฉบับจะรวมส่วนหัว (แต่เพียงผู้เดียว) ที่ประกาศไว้เสมอ
สำหรับตัวแปรที่กำหนดใด ๆ ไฟล์ต้นฉบับหนึ่งไฟล์จะกำหนดตัวแปรโดยควรกำหนดค่าเริ่มต้นด้วยเช่นกัน (แม้ว่าจะไม่จำเป็นต้องกำหนดค่าเริ่มต้นเป็นศูนย์อย่างชัดเจน แต่ก็ไม่เป็นอันตรายและสามารถทำได้ดีเนื่องจากมีคำจำกัดความเริ่มต้นเพียงหนึ่งเดียวของตัวแปรโกลบอลเฉพาะในโปรแกรม)
ไฟล์ต้นฉบับที่กำหนดตัวแปรรวมถึงส่วนหัวเพื่อให้แน่ใจว่าคำจำกัดความและการประกาศสอดคล้องกัน
extern
ฟังก์ชั่นไม่ควรต้องประกาศตัวแปรที่ใช้
หลีกเลี่ยงตัวแปรโกลบอลเมื่อทำได้ - ใช้ฟังก์ชันแทน
ซอร์สโค้ดและข้อความของคำตอบนี้มีอยู่ในที่เก็บ SOQ (คำถามซ้อนมากเกินไป) ของฉันบน GitHub ในไดเรกทอรีย่อยsrc / so-0143-3204
หากคุณไม่ใช่โปรแกรมเมอร์ C ที่มีประสบการณ์คุณสามารถ (และควรจะ) หยุดอ่านที่นี่
ด้วยคอมไพเลอร์ C (แน่นอนมาก) คุณสามารถหลีกเลี่ยงสิ่งที่เรียกว่านิยาม 'ทั่วไป' ของตัวแปรได้เช่นกัน 'Common' ที่นี่หมายถึงเทคนิคที่ใช้ใน Fortran สำหรับการแชร์ตัวแปรระหว่างไฟล์ต้นฉบับโดยใช้บล็อกสามัญ (อาจมีชื่อ) สิ่งที่เกิดขึ้นที่นี่คือไฟล์แต่ละไฟล์มีคำนิยามที่ไม่แน่นอนของตัวแปร ตราบใดที่ไม่มีไฟล์มากกว่าหนึ่งไฟล์ที่ให้คำจำกัดความที่ถูกกำหนดค่าเริ่มต้นแล้วไฟล์ต่างๆจะจบลงด้วยการแชร์คำจำกัดความทั่วไปของตัวแปร:
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#include "prog2.h"
#include <stdio.h>
long l = 9; /* Do not do this in portable code */
void put(void) { printf("l = %ld\n", l); }
เทคนิคนี้ไม่เป็นไปตามตัวอักษรของมาตรฐาน C และ 'กฎนิยามเดียว' - มันเป็นพฤติกรรมที่ไม่ได้กำหนดอย่างเป็นทางการ:
มีการใช้ตัวระบุที่มีการเชื่อมโยงภายนอก แต่ในโปรแกรมไม่มีคำจำกัดความภายนอกหนึ่งคำสำหรับตัวระบุหรือไม่ได้ใช้ตัวระบุและมีคำจำกัดความภายนอกหลายรายการสำหรับตัวระบุ (6.9)
นิยามภายนอกคือการประกาศภายนอกที่ยังเป็นความหมายของฟังก์ชั่น (นอกเหนือจากคำนิยามแบบอินไลน์) หรือวัตถุ หากตัวบ่งชี้ที่ประกาศพร้อมลิงก์ภายนอกถูกใช้ในนิพจน์ (นอกเหนือจากส่วนหนึ่งของตัวถูกดำเนินการของ
sizeof
หรือ_Alignof
โอเปอเรเตอร์ที่ผลลัพธ์เป็นค่าคงที่จำนวนเต็ม) ที่ใดที่หนึ่งในโปรแกรมทั้งหมดจะมีคำจำกัดความภายนอกอย่างแน่นอนสำหรับตัวระบุ มิฉะนั้นจะต้องมีมากกว่าหนึ่ง 161)161)ดังนั้นหากตัวบ่งชี้ที่ประกาศด้วยการเชื่อมโยงภายนอกไม่ได้ใช้ในการแสดงออกก็ไม่จำเป็นต้องมีคำจำกัดความภายนอก
อย่างไรก็ตามมาตรฐานซียังแสดงไว้ในภาคผนวกข้อมูล J เป็นหนึ่งในส่วนขยายที่พบบ่อย
อาจมีมากกว่าหนึ่งคำจำกัดความภายนอกสำหรับตัวระบุของวัตถุที่มีหรือไม่มีการใช้คำหลักภายนอกอย่างชัดเจน หากคำจำกัดความไม่เห็นด้วยหรือมากกว่าหนึ่งค่าเริ่มต้นพฤติกรรมที่ไม่ได้กำหนด (6.9.2)
เนื่องจากเทคนิคนี้ไม่ได้รับการสนับสนุนเสมอไปจึงเป็นการดีที่สุดที่จะหลีกเลี่ยงการใช้งานโดยเฉพาะอย่างยิ่งหากรหัสของคุณต้องพกพาได้ การใช้เทคนิคนี้คุณยังสามารถจบด้วยการสะกดคำแบบไม่ได้ตั้งใจ
หากไฟล์ใดไฟล์หนึ่งข้างต้นถูกประกาศว่าl
เป็น a double
แทนที่จะเป็น a long
ตัวลิงก์ประเภทที่ไม่ปลอดภัยของ C อาจไม่พบจุดที่ไม่ตรงกัน หากคุณอยู่ในเครื่องที่มี 64- บิตlong
และdouble
คุณจะไม่ได้รับคำเตือน บนเครื่องที่มีขนาด 32- บิตlong
และ 64- บิตdouble
คุณอาจได้รับคำเตือนเกี่ยวกับขนาดต่าง ๆ - ตัวเชื่อมโยงจะใช้ขนาดที่ใหญ่ที่สุดเหมือนกับที่โปรแกรม Fortran ใช้ขนาดใหญ่ที่สุดของบล็อกทั่วไป
โปรดทราบว่า GCC 10.1.0 ซึ่งเปิดตัวใน 2020-05-07 เปลี่ยนตัวเลือกการคอมไพล์เริ่มต้นที่ใช้-fno-common
ซึ่งหมายความว่าโดยค่าเริ่มต้นโค้ดด้านบนจะไม่เชื่อมโยงอีกต่อไปเว้นแต่คุณจะแทนที่ค่าเริ่มต้นด้วย-fcommon
(หรือใช้แอททริบิว ฯลฯ ) ดูลิงค์)
ไฟล์สองไฟล์ถัดไปทำให้สมบูรณ์สำหรับprog2
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#include "prog2.h"
#include <stdio.h>
int main(void)
{
inc();
put();
dec();
put();
dec();
put();
}
prog2
การใช้งานprog2.c
, file10.c
, file11.c
, ,file12.c
prog2.h
ดังที่ระบุไว้ในความคิดเห็นที่นี่และตามที่ระบุไว้ในคำตอบของฉันสำหรับคำถามที่คล้ายกันการใช้คำจำกัดความหลายตัวแปรทั่วโลกทำให้เกิดพฤติกรรมที่ไม่ได้กำหนด (J.2; .9.9.9) ซึ่งเป็นวิธีมาตรฐานในการพูดว่า สิ่งหนึ่งที่สามารถเกิดขึ้นได้คือโปรแกรมทำงานตามที่คุณคาดไว้ และ J.5.11 พูดว่า "คุณอาจโชคดีบ่อยกว่าที่คุณสมควรได้รับ" โดยประมาณ แต่โปรแกรมที่ต้องอาศัยคำจำกัดความหลายตัวของตัวแปร extern ไม่ว่าจะมีหรือไม่มีคีย์เวิร์ด 'extern' อย่างชัดเจนไม่ใช่โปรแกรมที่สอดคล้องอย่างเคร่งครัดและไม่รับประกันว่าจะทำงานได้ทุกที่ อย่างเท่าเทียมกัน: มันมีข้อผิดพลาดที่อาจหรืออาจไม่แสดงตัวเอง
แน่นอนว่ามีหลายวิธีที่แนวทางเหล่านี้อาจเสียหายได้ บางครั้งอาจมีเหตุผลที่ดีในการทำลายแนวทาง แต่โอกาสดังกล่าวผิดปกติอย่างยิ่ง
c int some_var; /* Do not do this in a header!!! */
หมายเหตุ 1: ถ้าส่วนหัวกำหนดตัวแปรโดยไม่มีextern
คีย์เวิร์ดดังนั้นแต่ละไฟล์ที่มีส่วนหัวจะสร้างนิยามที่แน่นอนของตัวแปร ดังที่ระบุไว้ก่อนหน้านี้มักจะใช้งานได้ แต่มาตรฐาน C ไม่รับประกันว่าจะทำงานได้
c int some_var = 13; /* Only one source file in a program can use this */
หมายเหตุ 2: หากส่วนหัวกำหนดและเริ่มต้นตัวแปรดังนั้นไฟล์ต้นฉบับเพียงไฟล์เดียวในโปรแกรมที่กำหนดสามารถใช้ส่วนหัวได้ เนื่องจากส่วนหัวมีไว้สำหรับการแบ่งปันข้อมูลเป็นหลักมันค่อนข้างโง่ที่จะสร้างขึ้นมาซึ่งสามารถใช้ได้เพียงครั้งเดียว
c static int hidden_global = 3; /* Each source file gets its own copy */
หมายเหตุ 3: หากส่วนหัวกำหนดตัวแปรคงที่ (มีหรือไม่มีการเริ่มต้น) จากนั้นแต่ละไฟล์ต้นฉบับจะจบลงด้วยรุ่นส่วนตัวของตัวแปร 'ทั่วโลก'
ตัวอย่างเช่นถ้าตัวแปรเป็นอาร์เรย์แบบซับซ้อนตัวอย่างเช่นอาจทำให้เกิดการทำซ้ำรหัสอย่างรุนแรง บางครั้งมันอาจเป็นวิธีที่สมเหตุสมผลในการทำให้เกิดผลกระทบบางอย่าง แต่นั่นก็ผิดปกติมาก
ใช้เทคนิคหัวข้อที่ฉันแสดงก่อน มันทำงานได้อย่างน่าเชื่อถือและทุกที่ โดยเฉพาะอย่างยิ่งโปรดทราบว่าส่วนหัวประกาศglobal_variable
รวมอยู่ในทุกไฟล์ที่ใช้งาน - รวมถึงส่วนที่กำหนดไว้ สิ่งนี้ทำให้มั่นใจได้ว่าทุกอย่างสอดคล้องกัน
ความกังวลที่คล้ายกันเกิดขึ้นกับการประกาศและการกำหนดฟังก์ชั่น - ใช้กฎแบบอะนาล็อก แต่คำถามนั้นเกี่ยวกับตัวแปรโดยเฉพาะดังนั้นฉันจึงเก็บคำตอบไว้กับตัวแปรเท่านั้น
หากคุณไม่ใช่โปรแกรมเมอร์ C ที่มีประสบการณ์คุณควรหยุดอ่านที่นี่
ปลายสายสำคัญ
ข้อกังวลหนึ่งที่บางครั้ง (และถูกต้องตามกฎหมาย) เกิดขึ้นเกี่ยวกับกลไก 'การประกาศในส่วนหัวคำจำกัดความในแหล่งที่มา' อธิบายไว้ที่นี่คือมีไฟล์สองไฟล์ที่จะซิงโครไนซ์ - ส่วนหัวและแหล่งที่มา โดยทั่วไปแล้วจะมีการติดตามด้วยการสังเกตว่าสามารถใช้แมโครเพื่อให้ส่วนหัวทำหน้าที่สองหน้าที่ - โดยปกติแล้วจะประกาศตัวแปร แต่เมื่อมีการตั้งค่าแมโครเฉพาะก่อนที่จะรวมส่วนหัวมันจะกำหนดตัวแปรแทน
ข้อกังวลอีกประการหนึ่งคือตัวแปรที่จำเป็นต้องกำหนดใน 'โปรแกรมหลัก' แต่ละรายการ นี่เป็นเรื่องจริงที่น่ากังวล คุณสามารถแนะนำไฟล์ต้นฉบับ C เพื่อกำหนดตัวแปรและลิงก์ไฟล์อ็อบเจ็กต์ที่สร้างกับแต่ละโปรแกรม
แบบแผนทั่วไปทำงานเช่นนี้โดยใช้ตัวแปรโกลบอลดั้งเดิมที่แสดงในfile3.h
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
void use_it(void)
{
printf("Global variable: %d\n", global_variable++);
}
ไฟล์สองไฟล์ถัดไปทำให้สมบูรณ์สำหรับprog3
:
extern void use_it(void);
extern int increment(void);
#include "file3a.h"
#include "prog3.h"
#include <stdio.h>
int main(void)
{
use_it();
global_variable += 19;
use_it();
printf("Increment: %d\n", increment());
return 0;
}
prog3
การใช้งานprog3.c
, file1a.c
, file2a.c
, ,file3a.h
prog3.h
ปัญหาของโครงร่างนี้ดังที่แสดงคือมันไม่ได้เตรียมไว้สำหรับการเริ่มต้นของตัวแปรโกลบอล ด้วย C99 หรือ C11 และรายการอาร์กิวเมนต์ตัวแปรสำหรับแมโครคุณสามารถกำหนดแมโครเพื่อสนับสนุนการเริ่มต้นได้เช่นกัน (ด้วย C89 และไม่รองรับรายการอาร์กิวเมนต์ตัวแปรในมาโครจึงไม่มีวิธีที่ง่ายในการจัดการกับ initializers ที่ยาวโดยพลการ)
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZER(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZER(...) /* nothing */
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable INITIALIZER(37);
EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 });
ย้อนกลับเนื้อหาของ#if
และ#else
บล็อกแก้ไขข้อผิดพลาดที่ระบุโดย
Denis Kniazhev
#define DEFINE_VARIABLES
#include "file3b.h" /* Variables now defined and initialized */
#include "prog4.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
เห็นได้ชัดว่ารหัสสำหรับโครงสร้างคี่บอลนั้นไม่ใช่สิ่งที่คุณเขียนตามปกติ แต่มันแสดงให้เห็นถึงจุด อาร์กิวเมนต์แรกไปภาวนาที่สองของการINITIALIZER
มี{ 41
และการโต้แย้งที่เหลือ (เอกพจน์ในตัวอย่างนี้) 43 }
คือ หากไม่มี C99 หรือการสนับสนุนที่คล้ายกันสำหรับรายการอาร์กิวเมนต์ตัวแปรสำหรับแมโครค่าเริ่มต้นที่จำเป็นต้องมีเครื่องหมายจุลภาคนั้นเป็นปัญหามาก
file3b.h
รวมส่วนหัวที่ถูกต้อง(แทนfileba.h
) ต่อ
Denis Kniazhev
ไฟล์สองไฟล์ถัดไปทำให้สมบูรณ์สำหรับprog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#include "file3b.h"
#include "prog4.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog4
การใช้งานprog4.c
, file1b.c
, file2b.c
, ,prog4.h
file3b.h
ส่วนหัวใด ๆ ควรได้รับการปกป้องจากการรวมใหม่ดังนั้นคำจำกัดความของประเภท (enum, struct หรือ union type หรือโดยทั่วไปพิมพ์ดีด) จะไม่ทำให้เกิดปัญหา เทคนิคมาตรฐานคือการห่อเนื้อหาของส่วนหัวในการ์ดส่วนหัวเช่น:
#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED
...contents of header...
#endif /* FILE3B_H_INCLUDED */
ส่วนหัวอาจรวมสองครั้งทางอ้อม ตัวอย่างเช่นหากfile4b.h
รวมถึงfile3b.h
คำจำกัดความประเภทที่ไม่ได้แสดงและfile1b.c
จำเป็นต้องใช้ทั้งส่วนหัวfile4b.h
และfile3b.h
จากนั้นคุณมีปัญหายุ่งยากในการแก้ไขเพิ่มเติม file4b.h
เห็นได้ชัดว่าคุณอาจแก้ไขรายการส่วนหัวที่จะรวมเพียง อย่างไรก็ตามคุณอาจไม่ทราบถึงการพึ่งพาภายใน - และรหัสควรจะยังคงทำงานต่อไป
ยิ่งไปกว่านั้นมันเริ่มมีเล่ห์เหลี่ยมเนื่องจากคุณอาจรวมfile4b.h
ก่อนหน้านี้file3b.h
เพื่อรวมการสร้างคำจำกัดความ แต่ตัวป้องกันส่วนหัวปกติบนfile3b.h
จะป้องกันไม่ให้ส่วนหัวถูกรวมใหม่
ดังนั้นคุณต้องรวมเนื้อหาอย่างfile3b.h
น้อยหนึ่งครั้งสำหรับการประกาศและอย่างน้อยหนึ่งครั้งสำหรับคำจำกัดความ แต่คุณอาจต้องการทั้งสองอย่างในหน่วยการแปลเดียว (TU - การรวมกันของไฟล์ต้นฉบับและส่วนหัวที่ใช้)
อย่างไรก็ตามสามารถทำได้ภายใต้ข้อ จำกัด ที่ไม่สมเหตุสมผลเกินไป มาแนะนำชื่อไฟล์ชุดใหม่:
external.h
สำหรับนิยามแมโคร EXTERN ฯลฯ
file1c.h
เพื่อกำหนดประเภท (โดยเฉพาะstruct oddball
ประเภทของoddball_struct
)
file2c.h
เพื่อกำหนดหรือประกาศตัวแปรโกลบอล
file3c.c
ซึ่งกำหนดตัวแปรทั่วโลก
file4c.c
ซึ่งเพียงแค่ใช้ตัวแปรทั่วโลก
file5c.c
ซึ่งแสดงให้เห็นว่าคุณสามารถประกาศและจากนั้นกำหนดตัวแปรทั่วโลก
file6c.c
ซึ่งแสดงให้เห็นว่าคุณสามารถกำหนดและจากนั้น (พยายาม) ประกาศตัวแปรทั่วโลก
ในตัวอย่างเหล่านี้file5c.c
และfile6c.c
รวมส่วนหัวโดยตรงfile2c.h
หลายครั้ง แต่นั่นเป็นวิธีที่ง่ายที่สุดในการแสดงให้เห็นว่ากลไกทำงาน หมายความว่าหากส่วนหัวนั้นถูกรวมทางอ้อมสองครั้งก็จะปลอดภัยเช่นกัน
ข้อ จำกัด สำหรับการทำงานนี้คือ:
ส่วนหัวที่กำหนดหรือประกาศตัวแปรทั่วโลกอาจไม่ได้กำหนดประเภทใด ๆ
ทันทีก่อนที่คุณจะมีส่วนหัวที่ควรกำหนดตัวแปรคุณจะต้องกำหนดแมโคร DEFINE_VARIABLES
ส่วนหัวที่กำหนดหรือประกาศตัวแปรมีเนื้อหาที่มีสไตล์
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED
struct oddball
{
int a;
int b;
};
extern void use_them(void);
extern int increment(void);
extern int oddball_value(void);
#endif /* FILE1C_H_INCLUDED */
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif
#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE2C_H_INCLUDED */
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#include "file2c.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
#include "file2c.h" /* Declare variables */
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
#define DEFINE_VARIABLES
#include "file2c.h" /* Variables now defined and initialized */
#include "file2c.h" /* Declare variables */
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
แฟ้มแหล่งที่มาต่อไปเสร็จสมบูรณ์แหล่งที่มา (มีโปรแกรมหลัก) สำหรับprog5
, prog6
และprog7
:
#include "file2c.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
prog5
การใช้งานprog5.c
, file3c.c
, file4c.c
, file1c.h
, ,file2c.h
external.h
prog6
การใช้งานprog5.c
, file5c.c
, file4c.c
, file1c.h
, ,file2c.h
external.h
prog7
การใช้งานprog5.c
, file6c.c
, file4c.c
, file1c.h
, ,file2c.h
external.h
โครงการนี้หลีกเลี่ยงปัญหาส่วนใหญ่ คุณพบปัญหาเฉพาะในกรณีที่ส่วนหัวที่กำหนดตัวแปร (เช่นfile2c.h
) รวมอยู่ในส่วนหัวอื่น (พูดfile7c.h
) ที่กำหนดตัวแปร ไม่มีวิธีง่าย ๆ ในการอื่นนอกเหนือจาก "อย่าทำ"
คุณสามารถแก้ไขปัญหาบางส่วนได้โดยแก้ไขfile2c.h
เป็นfile2d.h
:
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2D_H_DEFINITIONS)
#undef FILE2D_H_INCLUDED
#endif
#ifndef FILE2D_H_INCLUDED
#define FILE2D_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file1c.h" /* Type definition for struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });
#endif /* !DEFINE_VARIABLES || !FILE2D_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2D_H_DEFINITIONS
#undef DEFINE_VARIABLES
#endif /* DEFINE_VARIABLES */
#endif /* FILE2D_H_INCLUDED */
ปัญหากลายเป็น 'ส่วนหัวควรรวม#undef DEFINE_VARIABLES
หรือไม่' หากคุณไม่ใช้สิ่งนั้นจากส่วนหัวและตัดคำเชิญที่กำหนดด้วย#define
และ#undef
:
#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES
ในซอร์สโค้ด (ดังนั้นส่วนหัวไม่เคยเปลี่ยนแปลงค่าของDEFINE_VARIABLES
) จากนั้นคุณควรจะสะอาด มันเป็นเพียงความรำคาญที่ต้องจำไว้ว่าให้เขียนบรรทัดพิเศษ ทางเลือกอาจเป็น:
#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"
#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */
สิ่งนี้กำลังได้รับความสับสนเล็กน้อย แต่ดูเหมือนว่าจะปลอดภัย (ใช้โดยfile2d.h
ไม่มี#undef DEFINE_VARIABLES
ในfile2d.h
)
/* Declare variables */
#include "file2d.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Declare variables - again */
#include "file2d.h"
/* Define variables - again */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE8C_H_DEFINITIONS)
#undef FILE8C_H_INCLUDED
#endif
#ifndef FILE8C_H_INCLUDED
#define FILE8C_H_INCLUDED
#include "external.h" /* Support macros EXTERN, INITIALIZE */
#include "file2d.h" /* struct oddball */
#if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)
/* Global variable declarations / definitions */
EXTERN struct oddball another INITIALIZE({ 14, 34 });
#endif /* !DEFINE_VARIABLES || !FILE8C_H_DEFINITIONS */
/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE8C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */
#endif /* FILE8C_H_INCLUDED */
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file2d.h"
#include "externdef.h"
/* Define variables */
#define HEADER_DEFINING_VARIABLES "file8c.h"
#include "externdef.h"
int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }
ไฟล์สองไฟล์ถัดไปทำให้สมบูรณ์สำหรับprog8
และprog9
:
#include "file2d.h"
#include <stdio.h>
int main(void)
{
use_them();
global_variable += 19;
use_them();
printf("Increment: %d\n", increment());
printf("Oddball: %d\n", oddball_value());
return 0;
}
#include "file2d.h"
#include <stdio.h>
void use_them(void)
{
printf("Global variable: %d\n", global_variable++);
oddball_struct.a += global_variable;
oddball_struct.b -= global_variable / 2;
}
prog8
การใช้งานprog8.c
, ,file7c.c
file9c.c
prog9
การใช้งานprog8.c
, ,file8c.c
file9c.c
อย่างไรก็ตามปัญหาดังกล่าวไม่น่าเป็นไปได้ที่จะเกิดขึ้นในทางปฏิบัติโดยเฉพาะหากคุณใช้คำแนะนำมาตรฐาน
นิทรรศการนี้พลาดอะไรหรือเปล่า?
คำสารภาพ : โครงร่าง 'การหลีกเลี่ยงรหัสที่ซ้ำกัน' ที่ระบุไว้ที่นี่ได้รับการพัฒนาเนื่องจากปัญหานี้ส่งผลกระทบต่อรหัสบางอย่างที่ฉันทำงาน (แต่ไม่ได้เป็นเจ้าของ) และเป็นปัญหาที่น่ากังวลสำหรับโครงการที่ระบุไว้ในส่วนแรกของคำตอบ อย่างไรก็ตามรูปแบบดั้งเดิมทำให้คุณมีเพียงสองสถานที่ที่จะปรับเปลี่ยนเพื่อให้คำจำกัดความของตัวแปรและการประกาศตรงกันซึ่งเป็นขั้นตอนใหญ่ไปข้างหน้ามีการประกาศตัวแปร exernal กระจัดกระจายไปทั่วฐานรหัส (ซึ่งจริงๆสำคัญเมื่อมีไฟล์นับพันทั้งหมด) . อย่างไรก็ตามรหัสในไฟล์ที่มีชื่อfileNc.[ch]
(บวกexternal.h
และexterndef.h
) แสดงว่าสามารถทำงานได้ เห็นได้ชัดว่าคงไม่ยากที่จะสร้างสคริปต์ตัวสร้างส่วนหัวเพื่อให้แม่แบบมาตรฐานสำหรับตัวแปรที่กำหนดและประกาศไฟล์ส่วนหัว
NBโปรแกรมเหล่านี้เป็นของเล่นที่มีรหัสเพียงพอที่จะทำให้พวกเขาน่าสนใจเล็กน้อย มีการทำซ้ำภายในตัวอย่างที่สามารถลบออกได้ แต่ไม่ใช่เพื่อทำให้คำอธิบายการสอนง่ายขึ้น (ตัวอย่างเช่น: ความแตกต่างระหว่างprog5.c
และprog8.c
เป็นชื่อของหนึ่งในส่วนหัวที่รวมไว้มันเป็นไปได้ที่จะจัดระเบียบรหัสใหม่เพื่อให้main()
ฟังก์ชั่นไม่ได้ทำซ้ำ แต่มันจะปกปิดมากกว่าที่เปิดเผย)
foo.h
): #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }
เพื่อกำหนด initializer สำหรับอาร์เรย์, enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };
เพื่อให้ได้ขนาดของอาร์เรย์, และextern int foo[];
ประกาศอาร์เรย์ . เห็นได้ชัดว่าคำจำกัดความควรจะเป็นเพียงint foo[FOO_SIZE] = FOO_INITIALIZER;
แต่ขนาดไม่จำเป็นต้องรวมอยู่ในคำนิยาม นี่ทำให้คุณมีค่าคงที่จำนวนเต็ม, FOO_SIZE
.
extern
ตัวแปรคือการประกาศ (ขอบคุณ SBI สำหรับการแก้ไข) ของตัวแปรซึ่งกำหนดไว้ในหน่วยการแปลอีก นั่นหมายถึงหน่วยเก็บสำหรับตัวแปรถูกจัดสรรในไฟล์อื่น
สมมติว่าคุณมีสอง.c
-Files และtest1.c
test2.c
หากคุณกำหนดตัวแปรทั่วโลกint test1_var;
ในtest1.c
และคุณต้องการเข้าถึงตัวแปรนี้test2.c
คุณต้องใช้ในextern int test1_var;
test2.c
ตัวอย่างที่สมบูรณ์:
$ cat test1.c
int test1_var = 5;
$ cat test2.c
#include <stdio.h>
extern int test1_var;
int main(void) {
printf("test1_var = %d\n", test1_var);
return 0;
}
$ gcc test1.c test2.c -o test
$ ./test
test1_var = 5
extern int test1_var;
ไปint test1_var;
, ลิงเกอร์ (GCC 5.4.0) ยังคงผ่านไป ดังนั้นextern
จำเป็นจริง ๆ ในกรณีนี้
extern
ส่วนขยายที่มักใช้งานได้ - และใช้งานได้เฉพาะกับ GCC (แต่ GCC นั้นไกลเกินกว่าจะเป็นคอมไพเลอร์เพียงตัวเดียวที่รองรับมัน; คุณสามารถมองหา "J.5.11" หรือส่วน "วิธีที่ไม่ดีดังนั้น" ในคำตอบของฉัน (ฉันรู้ - มันคือยาว) และข้อความที่อยู่ใกล้ที่อธิบายมัน (หรือพยายามที่จะทำเช่นนั้น)
ภายนอกคือคำหลักที่คุณใช้เพื่อประกาศว่าตัวแปรนั้นอยู่ในหน่วยการแปลอื่น
ดังนั้นคุณสามารถตัดสินใจที่จะใช้ตัวแปรในหน่วยการแปลแล้วเข้าถึงจากตัวแปรอื่นจากนั้นในอันที่สองคุณประกาศว่าเป็นภายนอกและสัญลักษณ์จะได้รับการแก้ไขโดย linker
หากคุณไม่ประกาศว่าเป็นภายนอกคุณจะได้รับ 2 ตัวแปรชื่อเดียวกัน แต่ไม่เกี่ยวข้องเลยและข้อผิดพลาดของคำจำกัดความหลายตัวแปร
ฉันชอบคิดว่าตัวแปรภายนอกเป็นสัญญาที่คุณทำกับคอมไพเลอร์
เมื่อเจอกับ extern คอมไพเลอร์สามารถค้นหาประเภทของมันไม่ใช่ตำแหน่งที่ "อยู่" ดังนั้นจึงไม่สามารถแก้ไขการอ้างอิงได้
คุณกำลังบอกว่า "เชื่อฉันในเวลาลิงก์การอ้างอิงนี้จะแก้ไขได้"
extern บอกให้คอมไพเลอร์เชื่อใจคุณว่าหน่วยความจำสำหรับตัวแปรนี้ถูกประกาศไว้ที่อื่นดังนั้นจึงไม่ลองจัดสรร / ตรวจสอบหน่วยความจำ
ดังนั้นคุณสามารถรวบรวมไฟล์ที่มีการอ้างอิงไปยัง extern แต่คุณไม่สามารถเชื่อมโยงได้หากหน่วยความจำนั้นไม่ได้ถูกประกาศที่ไหนสักแห่ง
มีประโยชน์สำหรับตัวแปรและไลบรารีระดับโลก แต่มีอันตรายเนื่องจากตัวลิงก์ไม่ได้ตรวจสอบ
เพิ่มextern
เปลี่ยนตัวแปรนิยามเป็นตัวแปรประกาศ ดูกระทู้นี้ว่าอะไรคือความแตกต่างระหว่างการประกาศและคำจำกัดความ
declare | define | initialize |
----------------------------------
extern int a; yes no no
-------------
int a = 2019; yes yes yes
-------------
int a; yes yes no
-------------
การประกาศจะไม่จัดสรรหน่วยความจำ (ต้องกำหนดตัวแปรสำหรับการจัดสรรหน่วยความจำ) แต่จะมีการกำหนด นี่เป็นเพียงมุมมองง่ายๆเกี่ยวกับคำหลักภายนอกเนื่องจากคำตอบอื่น ๆ นั้นยอดเยี่ยมมาก
การตีความที่ถูกต้องของภายนอกคือคุณบอกบางสิ่งกับคอมไพเลอร์ คุณบอกคอมไพเลอร์ว่าแม้จะไม่มีอยู่ในปัจจุบันตัวแปรที่ประกาศจะพบได้โดย linker (โดยทั่วไปแล้วจะอยู่ในวัตถุอื่น (ไฟล์)) ลิงเกอร์จะเป็นคนโชคดีที่พบทุกสิ่งและรวบรวมมันเข้าด้วยกันไม่ว่าคุณจะมีการประกาศจากภายนอกหรือไม่ก็ตาม
ใน C ตัวแปรภายในไฟล์บอกว่า example.c ได้รับขอบเขตในตัวเครื่อง คอมไพเลอร์คาดหวังว่าตัวแปรจะมีคำจำกัดความของมันอยู่ในไฟล์เดียวกัน example.c และเมื่อมันไม่พบเหมือนกันมันก็จะโยนข้อผิดพลาดฟังก์ชั่นในมืออื่น ๆ ที่มีตามขอบเขตทั่วโลกเริ่มต้น ดังนั้นคุณไม่ต้องพูดถึงคอมไพเลอร์ "look dude ... คุณอาจพบคำจำกัดความของฟังก์ชันนี้ที่นี่" สำหรับฟังก์ชั่นรวมถึงไฟล์ที่มีการประกาศก็เพียงพอแล้ว (ไฟล์ที่คุณเรียกไฟล์ส่วนหัว) ตัวอย่างเช่นพิจารณาไฟล์ 2 ไฟล์ต่อไปนี้:
example.c
#include<stdio.h>
extern int a;
main(){
printf("The value of a is <%d>\n",a);
}
example1.c
int a = 5;
ตอนนี้เมื่อคุณคอมไพล์ไฟล์ทั้งสองเข้าด้วยกันโดยใช้คำสั่งต่อไปนี้:
ขั้นตอนที่ 1) cc -o ex example.c example1.c ขั้นตอนที่ 2) ./ ex
คุณได้ผลลัพธ์ต่อไปนี้: ค่าของ a คือ <5>
การใช้งาน GCC ELF Linux
คำตอบอื่น ๆ ได้กล่าวถึงมุมมองด้านการใช้ภาษาดังนั้นตอนนี้เรามาดูวิธีการนำไปใช้ในการใช้งานนี้
main.c
#include <stdio.h>
int not_extern_int = 1;
extern int extern_int;
void main() {
printf("%d\n", not_extern_int);
printf("%d\n", extern_int);
}
รวบรวมและถอดรหัส:
gcc -c main.c
readelf -s main.o
เอาท์พุทประกอบด้วย:
Num: Value Size Type Bind Vis Ndx Name
9: 0000000000000000 4 OBJECT GLOBAL DEFAULT 3 not_extern_int
12: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND extern_int
บทที่System V ABI Update ELF สเปค "ตารางสัญลักษณ์" อธิบาย:
SHN_UNDEF ดัชนีตารางส่วนนี้หมายความว่าไม่มีการกำหนดสัญลักษณ์ เมื่อตัวแก้ไขลิงก์รวมอ็อบเจ็กต์ไฟล์นี้เข้ากับอีกอันที่กำหนดสัญลักษณ์ที่ระบุการอ้างอิงของสัญลักษณ์นี้กับไฟล์จะถูกลิงก์กับนิยามจริง
ซึ่งโดยทั่วไปพฤติกรรมที่มาตรฐาน C ให้กับextern
ตัวแปร
จากนี้ไปมันเป็นหน้าที่ของ linker ในการสร้างโปรแกรมขั้นสุดท้าย แต่extern
ข้อมูลได้ถูกดึงออกมาจากซอร์สโค้ดไปยังไฟล์ออบเจ็กต์แล้ว
ทดสอบกับ GCC 4.8
ตัวแปรอินไลน์ C ++ 17
ใน C ++ 17 คุณอาจต้องการใช้ตัวแปรอินไลน์แทนตัวแปร extern เนื่องจากมันใช้ง่าย (สามารถกำหนดได้เพียงครั้งเดียวที่ส่วนหัว) และมีประสิทธิภาพมากกว่า (รองรับ constexpr) ดู: 'const static' หมายถึงอะไรใน C และ C ++
readelf
หรือnm
อาจมีประโยชน์คุณยังไม่ได้อธิบายพื้นฐานของวิธีการใช้ประโยชน์extern
หรือทำให้โปรแกรมแรกเสร็จสมบูรณ์พร้อมคำจำกัดความที่แท้จริง notExtern
รหัสของคุณไม่ได้ใช้ มีปัญหาเกี่ยวกับระบบการตั้งชื่อด้วยเช่นกัน: แม้ว่าจะnotExtern
มีการกำหนดไว้ที่นี่แทนที่จะประกาศด้วยextern
แต่มันเป็นตัวแปรภายนอกที่สามารถเข้าถึงได้โดยไฟล์ต้นฉบับอื่นหากหน่วยการแปลเหล่านั้นมีการประกาศที่เหมาะสม (ซึ่งต้องextern int notExtern;
!
notExtern
น่าเกลียดแก้ไขมัน เกี่ยวกับระบบการตั้งชื่อแจ้งให้เราทราบหากคุณมีชื่อที่ดีขึ้น แน่นอนว่าไม่ใช่ชื่อที่ดีสำหรับโปรแกรมจริง แต่ฉันคิดว่ามันเหมาะกับบทบาทการสอนที่นี่
global_def
ตัวแปรที่กำหนดไว้ที่นี่และextern_ref
ตัวแปรที่กำหนดในโมดูลอื่น พวกเขาจะมีความสมดุลที่ชัดเจนอย่างเหมาะสมหรือไม่ คุณยังท้ายint extern_ref = 57;
หรืออะไรทำนองนั้นในไฟล์ที่มีการกำหนดดังนั้นชื่อจึงไม่เหมาะ แต่ในบริบทของไฟล์ต้นฉบับมันเป็นตัวเลือกที่สมเหตุสมผล การมีextern int global_def;
ส่วนหัวไม่ได้เป็นปัญหามากนัก ขึ้นอยู่กับคุณทั้งหมดแน่นอน
คำหลักภายนอกจะใช้กับตัวแปรสำหรับการระบุว่าเป็นตัวแปรทั่วโลก
นอกจากนี้ยังแสดงให้เห็นว่าคุณสามารถใช้ตัวแปรประกาศโดยใช้คำหลัก extern ในไฟล์ใด ๆ แม้ว่ามันจะมีการประกาศ / กำหนดในไฟล์อื่น ๆ
extern
อนุญาตให้โมดูลหนึ่งของโปรแกรมของคุณเข้าถึงตัวแปรหรือฟังก์ชันส่วนกลางที่ประกาศในโมดูลอื่นของโปรแกรมของคุณ คุณมักจะประกาศตัวแปร extern ในไฟล์ส่วนหัว
หากคุณไม่ต้องการให้โปรแกรมเข้าถึงตัวแปรหรือฟังก์ชั่นของคุณคุณใช้static
ซึ่งบอกคอมไพเลอร์ว่าตัวแปรหรือฟังก์ชั่นนี้ไม่สามารถใช้งานได้นอกโมดูลนี้
extern
หมายถึงตัวแปรถูกกำหนดไว้ที่อื่น (เช่นในไฟล์อื่น)
ก่อนอื่นextern
คำสำคัญไม่ได้ใช้สำหรับการกำหนดตัวแปร ค่อนข้างจะใช้สำหรับการประกาศตัวแปร ฉันสามารถพูดได้ว่าextern
เป็นคลาสหน่วยเก็บข้อมูลไม่ใช่ประเภทข้อมูล
extern
ใช้เพื่อให้ไฟล์ C อื่น ๆ หรือส่วนประกอบภายนอกรู้ว่าตัวแปรนี้ถูกกำหนดไว้แล้วที่ไหนสักแห่ง ตัวอย่าง: หากคุณกำลังสร้างห้องสมุดไม่จำเป็นต้องกำหนดตัวแปรส่วนกลางโดยไม่ต้องมีคำสั่งอยู่ในห้องสมุด ไลบรารีจะถูกคอมไพล์โดยตรง แต่ในขณะที่เชื่อมโยงไฟล์มันจะตรวจสอบข้อกำหนด
extern
ถูกใช้เพื่อให้first.c
ไฟล์หนึ่งไฟล์สามารถเข้าถึงพารามิเตอร์โกลบอลได้อย่างสมบูรณ์ในsecond.c
ไฟล์อื่น
extern
สามารถประกาศในfirst.c
ไฟล์หรือในใด ๆ ของส่วนหัวของไฟล์first.c
รวมถึง
extern
ประกาศควรอยู่ในส่วนหัวไม่ใช่ในfirst.c
เพื่อที่ว่าหากการเปลี่ยนแปลงประเภทการประกาศจะเปลี่ยนแปลงเช่นกัน นอกจากนี้ส่วนหัวที่ประกาศตัวแปรควรรวมอยู่ด้วยsecond.c
เพื่อให้แน่ใจว่าคำจำกัดความสอดคล้องกับการประกาศ การประกาศในส่วนหัวเป็นกาวที่ยึดมันเข้าด้วยกัน มันช่วยให้ไฟล์ที่จะรวบรวมแยกต่างหาก แต่มั่นใจว่าพวกเขามีมุมมองที่สอดคล้องของประเภทของตัวแปรทั่วโลก
ด้วย xc8 คุณต้องระวังเกี่ยวกับการประกาศตัวแปรชนิดเดียวกันในแต่ละไฟล์เท่าที่จะทำได้ประกาศอย่างผิดพลาดบางสิ่งบางอย่างint
ในไฟล์เดียวและchar
พูดอีกอย่างหนึ่ง สิ่งนี้อาจนำไปสู่ความเสียหายของตัวแปร
ปัญหานี้ได้รับการแก้ไขอย่างหรูหราในฟอรัม microchip เมื่อ 15 ปีก่อน / * โปรดดู "http: www.htsoft.com" / / "ฟอรัม / all / showflat.php / Cat / 0 / หมายเลข / 18766 / an / 0 / หน้า / 0 # 18766"
แต่ดูเหมือนว่าลิงค์นี้จะไม่ทำงานอีกต่อไป ...
ดังนั้นฉันจะพยายามอธิบายอย่างรวดเร็ว ทำไฟล์ที่เรียกว่า global.h
ในการประกาศดังต่อไปนี้
#ifdef MAIN_C
#define GLOBAL
/* #warning COMPILING MAIN.C */
#else
#define GLOBAL extern
#endif
GLOBAL unsigned char testing_mode; // example var used in several C files
ตอนนี้อยู่ในไฟล์ main.c
#define MAIN_C 1
#include "global.h"
#undef MAIN_C
ซึ่งหมายความว่าใน main.c unsigned char
ตัวแปรจะได้รับการประกาศเป็น
ตอนนี้ในไฟล์อื่น ๆ รวมถึง global.h จะมีการประกาศเป็น extern สำหรับไฟล์นั้น
extern unsigned char testing_mode;
unsigned char
แต่มันจะได้รับการประกาศอย่างถูกต้องเป็น
โพสต์ฟอรัมเก่าอาจอธิบายสิ่งนี้ได้ชัดเจนยิ่งขึ้น แต่นี่เป็นศักยภาพที่แท้จริงgotcha
เมื่อใช้คอมไพเลอร์ที่ช่วยให้คุณสามารถประกาศตัวแปรในไฟล์หนึ่งแล้วประกาศว่าภายนอกเป็นประเภทอื่นในอีกไฟล์หนึ่ง ปัญหาที่เกี่ยวข้องกับการที่ถ้าคุณพูดประกาศ test_mode เป็น int ในไฟล์อื่นก็จะคิดว่ามันเป็น 16 บิต var และเขียนทับส่วนอื่น ๆ ของ ram, อาจทำให้เกิดความเสียหายตัวแปรอื่น การดีบักยาก!
โซลูชันสั้น ๆ ที่ฉันใช้เพื่ออนุญาตให้ไฟล์ส่วนหัวมีการอ้างอิงภายนอกหรือการใช้งานจริงของวัตถุ #define GLOBAL_FOO_IMPLEMENTATION
แฟ้มที่จริงมีวัตถุเพียงแค่ไม่ จากนั้นเมื่อฉันเพิ่มวัตถุใหม่ลงในไฟล์นี้มันจะปรากฏขึ้นในไฟล์นั้นโดยที่ฉันไม่ต้องคัดลอกและวางคำจำกัดความ
ฉันใช้รูปแบบนี้ในหลายไฟล์ ดังนั้นเพื่อที่จะทำให้สิ่งต่าง ๆ เป็นของตัวเองให้ได้มากที่สุดฉันแค่ใช้แมโคร GLOBAL เดี่ยวในแต่ละหัวข้อ ส่วนหัวของฉันมีลักษณะเช่นนี้:
//file foo_globals.h
#pragma once
#include "foo.h" //contains definition of foo
#ifdef GLOBAL
#undef GLOBAL
#endif
#ifdef GLOBAL_FOO_IMPLEMENTATION
#define GLOBAL
#else
#define GLOBAL extern
#endif
GLOBAL Foo foo1;
GLOBAL Foo foo2;
//file main.cpp
#define GLOBAL_FOO_IMPLEMENTATION
#include "foo_globals.h"
//file uses_extern_foo.cpp
#include "foo_globals.h