स्रोत फ़ाइलों के बीच चर साझा करने के लिए मैं बाहरी उपयोग कैसे करूं?


987

मुझे पता है कि C में वैश्विक चर कभी-कभी externकीवर्ड होते हैं। एक externचर क्या है ? घोषणा किस तरह की जाती है? इसका दायरा क्या है?

यह स्रोत फ़ाइलों में चर साझा करने से संबंधित है, लेकिन यह ठीक कैसे काम करता है? मैं कहां उपयोग externकरूं?

जवाबों:


1751

उपयोग करना externकेवल प्रासंगिकता का है जब आप जो प्रोग्राम बना रहे हैं, उसमें एक साथ कई स्रोत फाइलें शामिल हैं, जहां कुछ चर परिभाषित किए गए हैं, उदाहरण के लिए, स्रोत फ़ाइल file1.cमें अन्य स्रोत फ़ाइलों में संदर्भित होने की आवश्यकता है, जैसे file2.c

एक चर को परिभाषित करने और एक चर घोषित करने के बीच अंतर को समझना महत्वपूर्ण है :

  • एक चर घोषित किया जाता है जब संकलक को सूचित किया जाता है कि एक चर मौजूद है (और यह इसका प्रकार है); यह उस बिंदु पर चर के लिए भंडारण को आवंटित नहीं करता है।

  • एक चर को परिभाषित किया जाता है जब संकलक चर के लिए भंडारण आवंटित करता है।

आप कई बार एक चर घोषित कर सकते हैं (हालांकि एक बार पर्याप्त है); आप किसी दिए गए दायरे में इसे केवल एक बार परिभाषित कर सकते हैं। एक चर परिभाषा भी एक घोषणा है, लेकिन सभी चर घोषणाएं परिभाषाएं नहीं हैं।

वैश्विक चर घोषित करने और परिभाषित करने का सबसे अच्छा तरीका

वैश्विक चर घोषित करने और परिभाषित करने का स्वच्छ, विश्वसनीय तरीका है कि आप चर की extern घोषणा को रोकने के लिए हेडर फाइल का उपयोग कर सकते हैं ।

शीर्ष लेख में एक स्रोत फ़ाइल शामिल है जो चर को परिभाषित करती है और चर का संदर्भ देने वाली सभी स्रोत फ़ाइलों द्वारा। प्रत्येक प्रोग्राम के लिए, एक स्रोत फ़ाइल (और केवल एक स्रोत फ़ाइल) चर को परिभाषित करती है। इसी तरह, एक हेडर फ़ाइल (और केवल एक हेडर फ़ाइल) को चर घोषित करना चाहिए। हेडर फ़ाइल महत्वपूर्ण है; यह स्वतंत्र टीयू (अनुवाद इकाइयों - स्रोत फ़ाइलों के बारे में) के बीच क्रॉस-चेकिंग को सक्षम बनाता है और स्थिरता सुनिश्चित करता है।

यद्यपि इसे करने के अन्य तरीके हैं, यह विधि सरल और विश्वसनीय है। यह द्वारा प्रदर्शित किया जाता है file3.h, file1.cऔर file2.c:

file3.h

extern int global_variable;  /* Declaration of the variable */

file1.c

#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++; }

file2.c

#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फ़ंक्शन घोषणाओं के सामने उपयोग नहीं करना पसंद करते हैं ; संकलक परवाह नहीं करता है - और अंत में, न तो जब तक आप सुसंगत हैं, कम से कम एक स्रोत फ़ाइल के भीतर।

prog1.h

extern void use_it(void);
extern int increment(void);

prog1.c

#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सहस्राब्दी के मोड़ के बाद से उत्पादित के अधिकांश संस्करणों के साथ काम करेगा । यह विशेष रूप से जीएनयू मेक से बंधा नहीं है।

prog1.mk

# 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 - सत्य का एकल बिंदु)।

  • एक स्रोत फ़ाइल externमें चर की घोषणाएं कभी नहीं होती हैं - स्रोत फ़ाइलों में हमेशा एकमात्र (एकमात्र) हेडर शामिल होता है जो उन्हें घोषित करता है।

  • किसी भी दिए गए चर के लिए, वास्तव में एक स्रोत फ़ाइल चर को परिभाषित करती है, अधिमानतः इसे भी प्रारंभ करना। (हालांकि शून्य को स्पष्ट रूप से प्रारंभ करने की कोई आवश्यकता नहीं है, यह कोई नुकसान नहीं पहुंचाता है और कुछ अच्छा कर सकता है, क्योंकि एक कार्यक्रम में किसी विशेष वैश्विक चर की केवल एक ही प्रारंभिक परिभाषा हो सकती है)।

  • चर को परिभाषित करने वाली स्रोत फ़ाइल में यह सुनिश्चित करने के लिए हेडर भी शामिल है कि परिभाषा और घोषणा संगत हैं।

  • एक फ़ंक्शन का उपयोग करके एक चर घोषित करने की आवश्यकता नहीं होनी चाहिए extern

  • जब भी संभव हो वैश्विक चर से बचें - इसके बजाय कार्यों का उपयोग करें।

इस उत्तर का स्रोत कोड और पाठ मेरे SOQ (Stack Overflow Questions) भंडार में src / so-0143-3204 उप-निर्देशिका में उपलब्ध हैं।

यदि आप एक अनुभवी सी प्रोग्रामर नहीं हैं, तो आप (और शायद) को यहां पढ़ना बंद कर सकते हैं।

वैश्विक चर को परिभाषित करने का इतना अच्छा तरीका नहीं है

कुछ (वास्तव में, कई) सी संकलक के साथ, आप एक चर की 'आम' परिभाषा भी कह सकते हैं। Refers कॉमन ’, यहाँ, स्रोत फ़ाइलों के बीच चर साझा करने के लिए फोरट्रान में प्रयुक्त तकनीक को संदर्भित करता है, (संभवतः नाम) COMMON ब्लॉक का उपयोग करके। यहाँ क्या होता है कि प्रत्येक संख्या में फाइल चर की एक अस्थायी परिभाषा प्रदान करती है। जब तक एक से अधिक फ़ाइल एक आरंभिक परिभाषा प्रदान नहीं करती है, तब तक विभिन्न फाइलें चर की एक सामान्य एकल परिभाषा साझा करती हैं:

file10.c

#include "prog2.h"

long l; /* Do not do this in portable code */ 

void inc(void) { l++; }

file11.c

#include "prog2.h"

long l; /* Do not do this in portable code */ 

void dec(void) { l--; }

file12.c

#include "prog2.h"
#include <stdio.h>

long l = 9; /* Do not do this in portable code */ 

void put(void) { printf("l = %ld\n", l); }

यह तकनीक सी मानक के अक्षर और 'एक परिभाषा नियम' के अनुरूप नहीं है - यह आधिकारिक रूप से अपरिभाषित व्यवहार है:

J.2 अपरिभाषित व्यवहार

बाहरी लिंकेज के साथ एक पहचानकर्ता का उपयोग किया जाता है, लेकिन कार्यक्रम में पहचानकर्ता के लिए बिल्कुल एक बाहरी परिभाषा मौजूद नहीं है, या पहचानकर्ता का उपयोग नहीं किया जाता है और पहचानकर्ता (6.9) के लिए कई बाहरी परिभाषाएं मौजूद हैं।

§6.9 बाहरी परिभाषाएँ defin5

एक बाहरी परिभाषा एक बाहरी घोषणा है जो एक फ़ंक्शन (एक इनलाइन परिभाषा के अलावा) या एक वस्तु की परिभाषा भी है। एक पहचानकर्ता बाहरी लिंकेज के साथ घोषित (एक के संकार्य के हिस्से के रूप के अलावा अन्य एक अभिव्यक्ति में प्रयोग किया जाता है sizeofया _Alignofऑपरेटर जिसका परिणाम एक पूर्णांक स्थिर है), कहीं पूरे कार्यक्रम में वहाँ ठीक एक पहचानकर्ता के लिए बाहरी परिभाषा होगा; अन्यथा, एक से अधिक नहीं होंगे। 161)

161) इस प्रकार, यदि बाहरी लिंकेज के साथ घोषित एक पहचानकर्ता का अभिव्यक्ति में उपयोग नहीं किया जाता है, तो इसके लिए कोई बाहरी परिभाषा नहीं होनी चाहिए।

हालांकि, सी मानक भी यह जानकारीपूर्ण अनुलग्नक जम्मू में से एक के रूप में सूचीबद्ध आम एक्सटेंशन

J.5.11 एकाधिक बाहरी परिभाषाएँ

कीवर्ड बाहरी के स्पष्ट उपयोग के साथ या उसके बिना किसी वस्तु की पहचान करने वाले के लिए एक से अधिक बाहरी परिभाषा हो सकती है; यदि परिभाषाएँ असहमत हैं, या एक से अधिक का आरोप लगाया गया है, तो व्यवहार अपरिभाषित है (6.9.2)।

क्योंकि यह तकनीक हमेशा समर्थित नहीं है, इसलिए इसका उपयोग करने से बचने के लिए सबसे अच्छा है, खासकर यदि आपके कोड को पोर्टेबल होने की आवश्यकता है । इस तकनीक का उपयोग करते हुए, आप अनजाने प्रकार की चकाचौंध के साथ भी समाप्त हो सकते हैं।

यदि उपरोक्त फ़ाइलों में से lएक के doubleबजाय एक के रूप में घोषित किया गया है long, तो C के टाइप-असुरक्षित लिंकर्स शायद बेमेल नहीं दिखेंगे। यदि आप 64-बिट के साथ एक मशीन पर हैं longऔर double, आपको चेतावनी भी नहीं मिलेगी; 32-बिट longऔर 64-बिट वाली मशीन पर double, आपको संभवतः विभिन्न आकारों के बारे में एक चेतावनी मिलेगी - लिंकर सबसे बड़े आकार का उपयोग करेगा, ठीक उसी तरह जैसे कि फोरट्रान प्रोग्राम किसी भी सामान्य ब्लॉक का सबसे बड़ा आकार लेगा।

ध्यान दें कि GCC 10.1.0, जो कि 2020-05-07 को जारी किया गया था, उपयोग करने के लिए डिफ़ॉल्ट संकलन विकल्पों में परिवर्तन करता है -fno-common, जिसका अर्थ है कि डिफ़ॉल्ट रूप से, जब तक आप डिफ़ॉल्ट के साथ ओवरराइड नहीं करते -fcommon(या विशेषताओं का उपयोग करते हैं, आदि) से ऊपर कोड लिंक नहीं करता है - लिंक देखें)।


अगली दो फाइलें इसके लिए स्रोत को पूरा करती हैं prog2:

prog2.h

extern void dec(void);
extern void put(void);
extern void inc(void);

prog2.c

#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 §6.9)। चीजों में से एक यह हो सकता है कि कार्यक्रम आपकी अपेक्षा के अनुरूप व्यवहार करे; और J.5.11 कहते हैं, लगभग, "आप अधिक भाग्यशाली हो सकते हैं जितना आप लायक हैं"। लेकिन एक प्रोग्राम जो एक्सटर्नल वेरिएबल की कई परिभाषाओं पर निर्भर करता है - स्पष्ट 'एक्सटर्नल' कीवर्ड के साथ या उसके बिना - एक सख्ती से अनुरूप प्रोग्राम नहीं है और हर जगह काम करने की गारंटी नहीं है। समान रूप से: इसमें एक बग होता है जो स्वयं को दिखा सकता है या नहीं दिखा सकता है।

दिशानिर्देशों का उल्लंघन करना

बेशक, ऐसे कई तरीके हैं जिनसे इन दिशानिर्देशों को तोड़ा जा सकता है। कभी-कभी, दिशानिर्देशों को तोड़ने का एक अच्छा कारण हो सकता है, लेकिन ऐसे अवसर बेहद असामान्य होते हैं।

faulty_header.h

c int some_var; /* Do not do this in a header!!! */

नोट 1: यदि हेडर externकीवर्ड के बिना वेरिएबल को परिभाषित करता है , तो हेडर में प्रत्येक फ़ाइल में वेरिएबल की एक टेंपरेरी परिभाषा बनाई जाती है। जैसा कि पहले उल्लेख किया गया है, यह अक्सर काम करेगा, लेकिन सी मानक गारंटी नहीं देता है कि यह काम करेगा।

broken_header.h

c int some_var = 13; /* Only one source file in a program can use this */

नोट 2: यदि शीर्ष लेख चर को परिभाषित करता है और आरंभ करता है, तो दिए गए प्रोग्राम में केवल एक स्रोत फ़ाइल हेडर का उपयोग कर सकता है। चूंकि हेडर मुख्य रूप से जानकारी साझा करने के लिए होते हैं, इसलिए इसे बनाना थोड़ा मूर्खतापूर्ण है जो केवल एक बार उपयोग किया जा सकता है।

seldom_correct.h

c static int hidden_global = 3; /* Each source file gets its own copy */

नोट 3: यदि शीर्ष लेख एक स्थैतिक चर (आरंभ के साथ या बिना) को परिभाषित करता है, तो प्रत्येक स्रोत फ़ाइल 'वैश्विक' चर के अपने निजी संस्करण के साथ समाप्त होती है।

यदि चर वास्तव में एक जटिल सरणी है, उदाहरण के लिए, इससे कोड का अत्यधिक दोहराव हो सकता है। यह, कभी-कभी, कुछ प्रभाव को प्राप्त करने का एक समझदार तरीका हो सकता है, लेकिन यह बहुत ही असामान्य है।


सारांश

मैंने पहले दिखाई गई हेडर तकनीक का उपयोग करें। यह मज़बूती से और हर जगह काम करता है। ध्यान दें, विशेष रूप से, यह घोषित करने वाला शीर्षलेख global_variableप्रत्येक फ़ाइल में शामिल है जो इसका उपयोग करता है - इसमें वह भी शामिल है जो इसे परिभाषित करता है। यह सुनिश्चित करता है कि सब कुछ आत्मनिर्भर है।

समान कार्य घोषित करने और परिभाषित करने के साथ-साथ समान नियम लागू होते हैं। लेकिन सवाल विशेष रूप से चर के बारे में था, इसलिए मैंने केवल चर का जवाब रखा है।

मूल उत्तर का अंत

यदि आप एक अनुभवी सी प्रोग्रामर नहीं हैं, तो आपको शायद यहां पढ़ना बंद कर देना चाहिए।


लेट मेजर एडिशन

कोड डुप्लीकेशन से बचना

एक चिंता जो कभी-कभी (और वैध रूप से) हेडर में 'घोषणाओं, स्रोत में परिभाषा' के बारे में उठाई जाती है, यहां वर्णित है कि हेडर और स्रोत को सिंक्रनाइज़ करने के लिए दो फाइलें हैं। यह आमतौर पर एक अवलोकन के साथ पालन किया जाता है कि एक मैक्रो का उपयोग किया जा सकता है ताकि हेडर दोहरे कर्तव्य परोसता है - सामान्य रूप से चर घोषित करता है, लेकिन जब हेडर शामिल करने से पहले एक विशिष्ट मैक्रो सेट किया जाता है, तो यह चर को परिभाषित करता है।

एक और चिंता का विषय यह हो सकता है कि चर को कई 'मुख्य कार्यक्रमों' में से प्रत्येक में परिभाषित किया जाना चाहिए। यह सामान्य रूप से एक गंभीर चिंता है; आप बस चर को परिभाषित करने के लिए एक सी स्रोत फ़ाइल पेश कर सकते हैं और प्रत्येक प्रोग्राम के साथ उत्पादित ऑब्जेक्ट फ़ाइल को लिंक कर सकते हैं।

एक विशिष्ट योजना इस तरह से काम करती है, जिसमें मूल वैश्विक चर का उपयोग किया गया है file3.h:

file3a.h

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */

EXTERN int global_variable;

file1a.c

#define DEFINE_VARIABLES
#include "file3a.h"  /* Variable defined - but not initialized */
#include "prog3.h"

int increment(void) { return global_variable++; }

file2a.c

#include "file3a.h"
#include "prog3.h"
#include <stdio.h>

void use_it(void)
{
    printf("Global variable: %d\n", global_variable++);
}

अगली दो फाइलें इसके लिए स्रोत को पूरा करती हैं prog3:

prog3.h

extern void use_it(void);
extern int increment(void);

prog3.c

#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 और कोई समर्थन नहीं होने के कारण, मनमाने ढंग से लंबे आरंभीकरण को संभालने का कोई आसान तरीका नहीं है।)

file3b.h

#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ब्लॉक की सामग्री

file1b.c

#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; }

file2b.c

#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) प्रति डेनिस नियाज़ेव


अगली दो फाइलें इसके लिए स्रोत को पूरा करती हैं prog4:

prog4.h

extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);

prog4.c

#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

हेडर गार्ड

किसी भी हेडर को पुनर्जन्म के खिलाफ संरक्षित किया जाना चाहिए, ताकि प्रकार की परिभाषाएं (एनम, संरचना या संघ प्रकार या आमतौर पर टाइप किए गए) समस्या का कारण न बनें। मानक तकनीक हैडर के शरीर को हेडर गार्ड में लपेटना है जैसे:

#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घोषणाओं के लिए अधिकतम एक बार शरीर को शामिल करने की आवश्यकता है , और परिभाषाओं के लिए अधिकतम एक बार, लेकिन आपको एक ही अनुवाद इकाई (टीयू - स्रोत फ़ाइल का एक संयोजन और इसका उपयोग करने वाले शीर्ष लेख) दोनों की आवश्यकता हो सकती है।

चर परिभाषाओं के साथ कई समावेश

हालांकि, यह एक बहुत अनुचित अनुचित के अधीन किया जा सकता है। चलिए एक नए फ़ाइल नाम का सेट प्रस्तुत करते हैं:

  • 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कई बार शामिल करते हैं , लेकिन यह दिखाने का सबसे सरल तरीका है कि तंत्र काम करता है। इसका मतलब है कि अगर हेडर को अप्रत्यक्ष रूप से दो बार शामिल किया गया था, तो यह सुरक्षित भी होगा।

इस काम के लिए प्रतिबंध हैं:

  1. वैश्विक चरों को परिभाषित या घोषित करने वाला शीर्षलेख स्वयं किसी भी प्रकार को परिभाषित नहीं कर सकता है।

  2. इससे पहले कि आप वैरिएबल को परिभाषित करने वाले हेडर को शामिल करें, आप मैक्रो DEFINE_VARIABLES को परिभाषित करते हैं।

  3. वैरिएबल को परिभाषित या घोषित करने वाले हेडर में सामग्री की शैली होती है।

external.h


#ifdef DEFINE_VARIABLES
#define EXTERN              /* nothing */
#define INITIALIZE(...)     = __VA_ARGS__
#else
#define EXTERN              extern
#define INITIALIZE(...)     /* nothing */
#endif /* DEFINE_VARIABLES */

file1c.h

#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 */

file2c.h


/* 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 */

file3c.c

#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; }

file4c.c

#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;
}

file5c.c


#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; }

file6c.c


#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:

prog5.c

#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:

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"

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)।

file7c.c

/* 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; }

file8c.h

/* 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 */

file8c.c

/* 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:

prog8.c

#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;
}

file9c.c

#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


हालाँकि, अभ्यास में समस्याएँ अपेक्षाकृत कम होती हैं, खासकर यदि आप मानक सलाह लेते हैं

वैश्विक चर से बचें


क्या यह प्रदर्शनी कुछ भी याद आती है?

स्वीकारोक्ति : यहां उल्लिखित 'डुप्लीकेट कोड से बचने की योजना' को विकसित किया गया था क्योंकि समस्या कुछ कोड को प्रभावित करती है जिन पर मैं काम करता हूं (लेकिन खुद का नहीं), और जवाब के पहले भाग में उल्लिखित योजना के साथ एक चिंताजनक चिंता है। हालांकि, मूल योजना आपको चर परिभाषाओं और घोषणाओं को सिंक्रनाइज़ रखने के लिए संशोधित करने के लिए सिर्फ दो स्थानों के साथ छोड़ती है, जो कि कोड बेस में बिखरे हुए बाह्य चर घोषणाएं होने पर एक बड़ा कदम है (जो वास्तव में तब मायने रखता है जब कुल फाइलें हजारों हैं) । हालांकि, नाम fileNc.[ch](प्लस external.hऔर externdef.h) के साथ फाइलों में कोड से पता चलता है कि यह काम करने के लिए बनाया जा सकता है। स्पष्ट रूप से, यह एक हेडर जनरेटर स्क्रिप्ट बनाने के लिए मुश्किल नहीं होगा कि आप एक चर को परिभाषित करने और हेडर फ़ाइल की घोषणा के लिए मानकीकृत टेम्पलेट दें।

एनबी ये थोड़े पर्याप्त कोड के साथ खिलौना कार्यक्रम हैं जो उन्हें मामूली दिलचस्प बनाते हैं। ऐसे उदाहरणों के भीतर पुनरावृत्ति है जिन्हें हटाया जा सकता है, लेकिन शैक्षणिक व्याख्या को सरल बनाने के लिए नहीं है। (उदाहरण के लिए: के बीच अंतर prog5.cऔर prog8.cहेडर में से एक का नाम है जो शामिल हैं। यह कोड को पुनर्गठित करना संभव होगा ताकि main()फ़ंक्शन दोहराया नहीं गया था, लेकिन यह प्रकट की तुलना में अधिक छिपाएगा।)


3
@litb: सामान्य परिभाषा के लिए अनुलग्नक J.5.11 देखें - यह एक सामान्य विस्तार है।
जोनाथन लेफ्लर

3
@litb: और मैं मानता हूं कि इसे टाला जाना चाहिए - यही कारण है कि यह 'वैश्विक चर को परिभाषित करने के लिए इतना अच्छा तरीका नहीं' पर अनुभाग में है।
जोनाथन लेफ़लर

3
वास्तव में यह एक सामान्य विस्तार है, लेकिन इस पर भरोसा करने के लिए एक कार्यक्रम के लिए यह अपरिभाषित व्यवहार है। मैं अभी स्पष्ट नहीं था कि क्या आप कह रहे हैं कि यह C के अपने नियमों द्वारा अनुमत है। अब मैं देख रहा हूँ कि आप कह रहे हैं कि यह केवल एक सामान्य विस्तार है और इससे बचने के लिए यदि आपको अपने कोड की आवश्यकता है पोर्टेबल होने के लिए। इसलिए मैं तुम्हें संदेह के बिना उखाड़ सकता हूं। वास्तव में महान जवाब IMHO :)
जोहान्स स्काउब -

19
यदि आप शीर्ष पर रुकते हैं, तो यह सरल चीजों को सरल रखता है। जैसा कि आप आगे पढ़ते हैं, यह अधिक बारीकियों, जटिलताओं और विवरणों से संबंधित है। मैंने अभी कम अनुभवी सी प्रोग्रामर - या सी प्रोग्रामर जो विषय पहले से जानते हैं, के लिए दो 'शुरुआती रोक बिंदु' जोड़े हैं। यदि आपको पहले से ही उत्तर पता है, लेकिन आपको तकनीकी खराबी का पता चल गया है तो मुझे इसे पढ़ने की कोई आवश्यकता नहीं है।
जोनाथन लेफ़लर

4
@ सुपरकार्ट: यह मेरे लिए होता है कि आप सरणी आकार के लिए गणना मूल्य प्राप्त करने के लिए ( foo.h): #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }एरे के लिए इनिशियलाइज़र को परिभाषित करने, एरे enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };के आकार को प्राप्त करने और एरे extern int foo[];को घोषित करने के लिए C99 एरे के शाब्दिक का उपयोग कर सकते हैं। । स्पष्ट रूप से, परिभाषा बस होनी चाहिए int foo[FOO_SIZE] = FOO_INITIALIZER;, हालांकि आकार को वास्तव में परिभाषा में शामिल करने की आवश्यकता नहीं है। यह आपको एक पूर्णांक लगातार हो जाता है FOO_SIZE
जोनाथन लेफ्लर

125

एक 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

21
कोई "छद्म परिभाषाएं" नहीं है। यह एक घोषणा है।
sbi

3
उपरोक्त उदाहरण में, अगर मैं बदल extern int test1_var;करने के लिए int test1_var;, लिंकर (जीसीसी 5.4.0) अभी भी गुजरता है। तो, क्या externवास्तव में इस मामले में जरूरत है?
रेडियोधर्म

2
@radiohead: मेरे जवाब में , आपको जानकारी मिलेगी कि गिराना externएक सामान्य विस्तार है जो अक्सर काम करता है - और विशेष रूप से जीसीसी के साथ काम करता है (लेकिन जीसीसी एकमात्र कंपाइलर होने से बहुत दूर है जो इसका समर्थन करता है; यह यूनिक्स सिस्टम पर प्रचलित है)। आप "J.5.11" या अनुभाग "इतना अच्छा नहीं जिस तरह से" मेरा उत्तर में लिए देख सकते हैं (मुझे पता है - यह है लंबे समय तक) और है कि निकट पाठ यह बताते हैं (या कोशिश करता ऐसा करने के लिए)।
जोनाथन लेफ़लर

एक बाहरी घोषणा निश्चित रूप से किसी अन्य अनुवाद इकाई में परिभाषित नहीं होती है (और आमतौर पर नहीं होती है)। वास्तव में, घोषणा और परिभाषा एक और एक ही हो सकती है।
मोनिका

40

एक्सटर्न्स वह कीवर्ड होता है जिसका उपयोग आप यह घोषित करने के लिए करते हैं कि वेरिएबल स्वयं किसी अन्य ट्रांसलेशन यूनिट में रहता है।

तो आप एक अनुवाद इकाई में एक चर का उपयोग करने का फैसला कर सकते हैं और फिर इसे दूसरे से एक्सेस कर सकते हैं, फिर दूसरे में आप इसे बाहरी रूप में घोषित करते हैं और लिंकर द्वारा प्रतीक को हल किया जाएगा।

यदि आप इसे बाहरी घोषित नहीं करते हैं, तो आपको 2 वैरिएबल मिलेंगे, जिनका नाम एक ही है, लेकिन सभी संबंधित नहीं हैं, और वेरिएबल की कई परिभाषाओं की त्रुटि है।


5
दूसरे शब्दों में, अनुवाद इकाई जहां बाहरी का उपयोग किया जाता है, इस चर, इसके प्रकार आदि के बारे में जानता है और इसलिए अंतर्निहित तर्क में स्रोत कोड को इसका उपयोग करने की अनुमति देता है, लेकिन यह चर को आवंटित नहीं करता है , एक अन्य अनुवाद इकाई ऐसा करेगी। यदि दोनों अनुवाद इकाइयाँ सामान्य रूप से चर घोषित करती हैं, तो संकलित कोड के भीतर संबंधित "गलत" संदर्भों के साथ और लिंकर के लिए परिणामी अस्पष्टता के साथ, चर के लिए प्रभावी रूप से दो भौतिक स्थान होंगे।
मगव

26

मैं एक बाहरी चर को एक वादे के रूप में सोचना पसंद करता हूं जो आप संकलक से करते हैं।

जब एक बाहरी मुठभेड़, संकलक केवल अपने प्रकार का पता लगा सकता है, न कि जहां यह "रहता है", इसलिए यह संदर्भ को हल नहीं कर सकता है।

आप इसे बता रहे हैं, "मुझ पर भरोसा करें। लिंक समय पर यह संदर्भ फिर से शुरू होगा।"


आम तौर पर, एक घोषणा एक वादा है कि नाम लिंक समय में एक ठीक एक परिभाषा के समाधान योग्य हो जाएगा। एक बाहरी परिभाषित करने के बिना एक बाहरी घोषित करता है।
रयान

18

बाहरी आपको संकलक को यह विश्वास दिलाने के लिए कहता है कि इस चर के लिए मेमोरी कहीं और घोषित की गई है, इसलिए यह मेमोरी को आवंटित / जांचने की कोशिश नहीं करता है।

इसलिए, आप उस फ़ाइल को संकलित कर सकते हैं जिसमें एक बाहरी का संदर्भ है, लेकिन आप लिंक नहीं कर सकते हैं यदि उस मेमोरी को कहीं घोषित नहीं किया गया है।

वैश्विक चर और पुस्तकालयों के लिए उपयोगी है, लेकिन खतरनाक है क्योंकि लिंकर चेक टाइप नहीं करता है।


स्मृति घोषित नहीं है। इस प्रश्न के उत्तर देखें: अधिक जानकारी के लिए stackoverflow.com/questions/1410563
sbi

15

जोड़ा जा रहा है एक externएक चर बदल जाता है परिभाषा एक चर में घोषणाइस धागे को देखें कि घोषणा और परिभाषा में क्या अंतर है।


( int fooऔर extern int fooफ़ाइल स्कोप) के बीच क्या अंतर है ? दोनों घोषणा है, है ना?

@ user14284: वे दोनों केवल इस अर्थ में घोषणा कर रहे हैं कि प्रत्येक परिभाषा एक घोषणा भी है। लेकिन मैं इसके स्पष्टीकरण से जुड़ा रहा। ("इस सूत्र को देखें कि घोषणा और परिभाषा में क्या अंतर है?") आप लिंक का अनुसरण करके सरल क्यों नहीं पढ़ते?
भारतीय स्टेट बैंक

14
                 declare | define   | initialize |
                ----------------------------------

extern int a;    yes          no           no
-------------
int a = 2019;    yes          yes          yes
-------------
int a;           yes          yes          no
-------------

घोषणा स्मृति आवंटित नहीं करेगी (स्मृति आवंटन के लिए चर को परिभाषित किया जाना चाहिए) लेकिन परिभाषा होगी। यह बाहरी कीवर्ड पर सिर्फ एक और सरल दृश्य है क्योंकि अन्य उत्तर वास्तव में बहुत अच्छे हैं।


11

बाहरी की सही व्याख्या यह है कि आप संकलक को कुछ बताते हैं। आप संकलक को बताते हैं कि, अभी मौजूद नहीं होने के बावजूद, घोषित चर किसी तरह लिंकर (आमतौर पर किसी अन्य ऑब्जेक्ट (फ़ाइल)) में पाया जाएगा। लिंकर फिर सब कुछ खोजने और इसे एक साथ रखने के लिए भाग्यशाली व्यक्ति होगा, चाहे आपके पास कुछ बाहरी घोषणाएं थीं या नहीं।


8

C में एक फ़ाइल के अंदर एक चर का उदाहरण example.c को स्थानीय गुंजाइश दी गई है। कंपाइलर को उम्मीद है कि वैरिएबल की फाइल के अंदर वैरिएबल की परिभाषा होगी। जब उसे समान नहीं मिलेगा, तो वह एक एरर फेंक देगा। दूसरी तरफ फंक्शन डिफ़ॉल्ट ग्लोबल स्कोप द्वारा होता है। इस प्रकार आपको संकलक को स्पष्ट रूप से उल्लेख नहीं करना है "यार देखो ... तुम्हें इस समारोह की परिभाषा यहाँ मिल सकती है"। फ़ाइल सहित एक फ़ंक्शन के लिए जिसमें इसकी घोषणा पर्याप्त है। (वह फ़ाइल जिसे आप वास्तव में हेडर फ़ाइल कहते हैं)। उदाहरण के लिए निम्नलिखित 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> है


8

जीसीसी ईएलएफ लिनक्स कार्यान्वयन

अन्य उत्तरों ने भाषा के उपयोग के पक्ष को कवर किया है, तो अब इस पर एक नज़र डालते हैं कि इसे कैसे लागू किया जाता है।

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

सिस्टम वी ABI अद्यतन ELF कल्पना "प्रतीक तालिका" अध्याय बताते हैं:

SHN_UNDEF इस खंड तालिका सूचकांक का अर्थ है कि प्रतीक अपरिभाषित है। जब लिंक संपादक इस ऑब्जेक्ट फ़ाइल को किसी अन्य के साथ जोड़ता है जो संकेतित प्रतीक को परिभाषित करता है, तो प्रतीक के लिए इस फ़ाइल के संदर्भ वास्तविक परिभाषा से जुड़े होंगे।

जो मूल रूप से व्यवहार सी मानक के देता है externचर।

अब से, अंतिम प्रोग्राम बनाने के लिए लिंकर का काम है, लेकिन externस्रोत कोड से ऑब्जेक्ट फ़ाइल में जानकारी पहले ही निकाली जा चुकी है।

जीसीसी 4.8 पर परीक्षण किया गया।

सी ++ 17 इनलाइन चर

C ++ 17 में, आप बाहरी लोगों के बजाय इनलाइन चर का उपयोग करना चाह सकते हैं, क्योंकि वे उपयोग करने के लिए सरल हैं (हेडर पर सिर्फ एक बार परिभाषित किया जा सकता है) और अधिक शक्तिशाली (समर्थन कॉन्स्ट्रेप)। देखें: C और C ++ में 'स्थैतिक स्थैतिक' का क्या अर्थ है?


3
यह मेरा डाउन-वोट नहीं है, इसलिए मुझे नहीं पता। हालाँकि, मैं एक राय रखूंगा। हालाँकि, का आउटपुट देखना readelfया nmमददगार हो सकता है, आपने इसका उपयोग नहीं करने के मूल सिद्धांतों के बारे में नहीं बताया है externऔर न ही वास्तविक परिभाषा के साथ पहला कार्यक्रम पूरा किया है। आपका कोड भी उपयोग नहीं करता है notExtern। एक नामकरण की समस्या है, भी: हालांकि notExternयहां घोषित के बजाय परिभाषित किया गया है extern, यह एक बाहरी चर है जिसे अन्य स्रोत फ़ाइलों द्वारा एक्सेस किया जा सकता है अगर उन अनुवाद इकाइयों में एक उपयुक्त घोषणा (जिसमें आवश्यकता होगी extern int notExtern;!)।
जोनाथन लेफ्लर 14

1
@JonathanLeffler प्रतिक्रिया के लिए धन्यवाद! मानक व्यवहार और उपयोग की सिफारिशें पहले से ही अन्य उत्तरों में की गई हैं, इसलिए मैंने कार्यान्वयन को थोड़ा दिखाने का फैसला किया क्योंकि वास्तव में मुझे यह समझने में मदद मिली कि क्या चल रहा है। notExternबदसूरत का उपयोग नहीं , यह तय किया गया था। नामकरण के बारे में, मुझे बताएं कि क्या आपका कोई बेहतर नाम है। बेशक, यह एक वास्तविक कार्यक्रम के लिए एक अच्छा नाम नहीं होगा, लेकिन मुझे लगता है कि यह अच्छी तरह से यहां प्रचलित भूमिका को फिट करता है।
सिरो सेंटिल्ली 冠状 i i i 法轮功 ''

जैसा कि नाम है, global_defयहाँ परिभाषित चर के लिए क्या है, और extern_refकुछ अन्य मॉड्यूल में परिभाषित चर के लिए? क्या उनके पास उपयुक्त समरूपता होगी? आप अभी भी int extern_ref = 57;उस फ़ाइल के साथ या उस चीज़ के साथ समाप्त होते हैं जहाँ इसे परिभाषित किया गया है, इसलिए नाम काफी आदर्श नहीं है, लेकिन एकल स्रोत फ़ाइल के संदर्भ में, यह एक उचित विकल्प है। extern int global_def;एक हेडर में होने के कारण एक समस्या नहीं है, यह मुझे लगता है। निश्चित रूप से आप पर निर्भर है।
जोनाथन Leffler

7

बाहरी कीवर्ड का उपयोग वैरिएबल वैरिएबल के रूप में इसकी पहचान के लिए वेरिएबल के साथ किया जाता है।

यह यह भी दर्शाता है कि आप किसी भी फ़ाइल में एक्सटर्नल कीवर्ड का उपयोग करके घोषित चर का उपयोग कर सकते हैं, हालांकि यह अन्य फ़ाइल में घोषित / परिभाषित है।


5

extern आपके प्रोग्राम के एक मॉड्यूल को आपके प्रोग्राम के किसी अन्य मॉड्यूल में घोषित वैश्विक चर या फ़ंक्शन तक पहुंचने की अनुमति देता है। आपके पास आमतौर पर हेडर फ़ाइलों में घोषित बाहरी चर होते हैं।

यदि आप अपने चर या कार्यों तक पहुँचने के लिए कोई कार्यक्रम नहीं चाहते हैं, तो आप staticसंकलक को यह बताते हैं कि इस मॉड्यूल के बाहर इस चर या फ़ंक्शन का उपयोग नहीं किया जा सकता है।


5

extern बस इसका मतलब है कि एक चर कहीं और परिभाषित किया गया है (जैसे, किसी अन्य फ़ाइल में)।


4

सबसे पहले, externकीवर्ड एक चर को परिभाषित करने के लिए नहीं किया जाता है; बल्कि इसका उपयोग चर घोषित करने के लिए किया जाता है। मैं कह सकता हूं कि externभंडारण वर्ग है, डेटा प्रकार नहीं।

externअन्य C फ़ाइलों या बाहरी घटकों को यह बताने के लिए उपयोग किया जाता है कि यह चर पहले से ही कहीं न कहीं परिभाषित है। उदाहरण: यदि आप एक पुस्तकालय का निर्माण कर रहे हैं, तो पुस्तकालय में कहीं पर भी वैश्विक परिवर्तनशीलता को परिभाषित करने की आवश्यकता नहीं है। लाइब्रेरी को सीधे संकलित किया जाएगा, लेकिन फ़ाइल को लिंक करते समय, यह परिभाषा की जांच करता है।


3

externउपयोग किया जाता है इसलिए एक first.cफ़ाइल में किसी अन्य second.cफ़ाइल में वैश्विक पैरामीटर तक पूर्ण पहुंच हो सकती है ।

externमें घोषित किया जा सकता first.cफ़ाइल या हेडर फाइल में से किसी में first.cभी शामिल है।


3
ध्यान दें कि externघोषणा एक हेडर में होनी चाहिए, न first.cकि इसलिए कि यदि प्रकार बदल जाता है, तो घोषणा भी बदल जाएगी। इसके अलावा, हैडर जो घोषणा करता है, चर second.cको यह सुनिश्चित करने के लिए शामिल किया जाना चाहिए कि परिभाषा घोषणा के अनुरूप है। शीर्षलेख में घोषणा वह गोंद है जो इसे एक साथ रखता है; यह फ़ाइलों को अलग-अलग संकलित करने की अनुमति देता है, लेकिन यह सुनिश्चित करता है कि उनके पास वैश्विक चर के प्रकार के अनुरूप दृश्य हो।
जोनाथन लेफ्लर 15

2

Xc8 के साथ आपको प्रत्येक फ़ाइल में एक चर के रूप में घोषित करने के बारे में सावधान रहना होगा, जैसा कि आप, गलती से, intएक फ़ाइल में कुछ और charदूसरे में एक कहने की घोषणा कर सकते हैं । इससे चर का भ्रष्टाचार हो सकता है।

यह समस्या सुंदर ढंग से कुछ 15 साल पहले एक माइक्रोचिप मंच में हल किया गया था / * देखें "http: www.htsoft.com" / / "मंच / सब / showflat.php / कैट / 0 / संख्या / 18,766 / एक / 0 / पेज / 0 # 18,766 "

लेकिन यह लिंक अब काम नहीं कर रहा है ...

तो मैं जल्दी से इसे समझाने की कोशिश करूँगा; 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

यह मुख्य साधन में है। चर को ए के रूप में घोषित किया जाएगा unsigned char

अब ग्लोबल सहित अन्य फाइलों में। यह उस फाइल के लिए एक बाहरी के रूप में घोषित किया जाएगा ।

extern unsigned char testing_mode;

लेकिन इसे सही रूप में घोषित किया जाएगा unsigned char

पुराने फ़ोरम पोस्ट ने शायद इसे थोड़ा और स्पष्ट रूप से समझाया। लेकिन gotchaकंपाइलर का उपयोग करते समय यह एक वास्तविक क्षमता है जो आपको एक फ़ाइल में एक चर घोषित करने की अनुमति देता है और फिर दूसरे में एक अलग प्रकार के रूप में बाहरी घोषित करता है। इससे जुड़ी समस्याएं यह हैं कि यदि आप कहते हैं कि परीक्षण_मोड को एक अन्य फ़ाइल में एक इंट के रूप में घोषित किया गया है तो उसे लगता है कि यह एक 16 बिट संस्करण था और राम के कुछ अन्य भाग को अधिलेखित करता है, संभवतः एक और चर को भ्रष्ट करता है। डिबग करना मुश्किल!


0

एक बहुत ही कम समाधान मैं एक हेडर फ़ाइल को किसी वस्तु के बाहरी संदर्भ या वास्तविक कार्यान्वयन में शामिल करने की अनुमति देता हूं। फ़ाइल जिसमें वास्तव में ऑब्जेक्ट होता है वह बस करता है #define GLOBAL_FOO_IMPLEMENTATION। फिर जब मैं इस फाइल में एक नई वस्तु जोड़ता हूं तो यह उस फाइल में दिखाई देता है, वह भी बिना मेरे द्वारा परिभाषा को कॉपी और पेस्ट किए बिना।

मैं कई फाइलों में इस पैटर्न का उपयोग करता हूं। तो आदेश के रूप में स्वयं संभव के रूप में निहित बातें रखने के लिए, मैं सिर्फ प्रत्येक शीर्षक में एक वैश्विक मैक्रो का पुन: उपयोग। मेरा हैडर इस तरह दिखता है:

//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
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.