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




global-variables extern (12)

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

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

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

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

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


मैंने निम्नलिखित चित्रों में सभी परिदृश्यों (कार्यों और चर के लिए) दिखाने की कोशिश की:


extern simply means a variable is defined elsewhere (ie, in another file).


एक extern चर एक परिवर्तनीय (एक सुधार के लिए एसबीआई के लिए धन्यवाद) है जो एक अन्य अनुवाद इकाई में परिभाषित किया गया है। इसका मतलब है कि चर के लिए भंडारण किसी अन्य फ़ाइल में आवंटित किया गया है।

मान लें कि आपके पास दो .c test2.c और test2.c । यदि आप वैश्विक वैरिएबल int test1_var; को परिभाषित int test1_var; test2.c आप test2.c में इस चर को एक्सेस करना चाहते हैं, आपको extern int test1_var; का उपयोग 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

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

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

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


लेकिन यह ठीक से कैसे काम करता है?

चलो देखते हैं कि कैसे जीसीसी 4.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);
}

संकलन और decompile:

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

सिस्टम वी एबीआई अपडेट ईएलएफ स्पेक "प्रतीक तालिका" अध्याय बताता है:

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

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

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


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

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


extern allows one module of your program to access a global variable or function declared in another module of your program. You usually have extern variables declared in header files.

If you don't want a program to access your variables or functions, you use static which tells the compiler that this variable or function cannot be used outside of this module.


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

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

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

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

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

यद्यपि ऐसा करने के अन्य तरीके हैं, वैश्विक चर घोषित करने और परिभाषित करने के लिए स्वच्छ, विश्वसनीय तरीका चर के file3.h घोषणा को शामिल करने के लिए हेडर फ़ाइल फ़ाइल file3.h का उपयोग करना है। हेडर को एक स्रोत फ़ाइल द्वारा शामिल किया गया है जो चर को संदर्भित करता है और वेरिएबल को संदर्भित करने वाली सभी स्रोत फ़ाइलों द्वारा परिभाषित करता है। प्रत्येक प्रोग्राम के लिए, एक स्रोत फ़ाइल (और केवल एक स्रोत फ़ाइल) चर को परिभाषित करती है। इसी तरह, एक हेडर फ़ाइल (और केवल एक हेडर फ़ाइल) को चर घोषित करना चाहिए।

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 लिए स्रोत को पूरा करती हैं:

ध्यान दें कि हेडर में परिवर्तनीय घोषणाओं के सामने बाहरी से मिलान करने के लिए मैं हेडर में फ़ंक्शन घोषणाओं के सामने कीवर्ड extern उपयोग करता हूं (उदाहरण के लिए prog1.h में दिखाया गया है)। कई लोग कार्यों के सामने extern का उपयोग नहीं करना पसंद करते हैं; कंपाइलर परवाह नहीं है - और आखिरकार, जब तक आप सुसंगत न हों तब तक न करें।

prog1.h

extern void use_it(void);   // "extern" is optional here; see note above
extern int increment(void); // "extern" is optional here; see note above

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 file1.c , file2.c , file2.c , file3.h और prog1.h का उपयोग करता है।

दिशा-निर्देश

केवल विशेषज्ञों द्वारा तोड़ने के नियम, और केवल अच्छे कारण के साथ:

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

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

इस उत्तर का स्रोत कोड और टेक्स्ट src/so-0143-3204 उप-निर्देशिका में src/so-0143-3204 पर मेरे SOQ (स्टैक ओवरफ़्लो प्रश्न) भंडार में उपलब्ध है।

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

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

file10.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void inc(void) { i++; }

file11.c

#include "prog2.h"

int i;   /* Do not do this in portable code */

void dec(void) { i--; }

file12.c

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

int i = 9;   /* Do not do this in portable code */

void put(void) { printf("i = %d\n", i); }

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

यह एक सामान्य विस्तार के रूप में सूचनात्मक अनुलग्नक जे में सी मानक में उल्लेख किया गया है:

जे.5.11 एकाधिक बाहरी परिभाषाएं

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

अगली दो फाइलें prog2 2 के लिए स्रोत को पूरा 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 का उपयोग करता है।

चेतावनी

जैसा कि यहां टिप्पणियों में उल्लेख किया गया है, और जैसा कि एक समान question उत्तर में बताया गया है, वैश्विक वैरिएबल के लिए कई परिभाषाओं का उपयोग करने से अनिर्धारित व्यवहार होता है, जो "कुछ भी हो सकता है" कहने का मानक तरीका है। ऐसी चीजों में से एक यह है कि कार्यक्रम की अपेक्षा के अनुसार व्यवहार करता है; और जे.5.11 कहता है, लगभग, "आप लायक होने के मुकाबले अधिक भाग्यशाली हो सकते हैं"। लेकिन एक प्रोग्राम जो बाहरी चर के कई परिभाषाओं पर निर्भर करता है - स्पष्ट 'बाहरी' कीवर्ड के साथ या उसके बिना - सख्ती से अनुरूप कार्यक्रम नहीं है और हर जगह काम करने की गारंटी नहीं देता है। समानता: इसमें एक बग है जो स्वयं को दिखा सकता है या नहीं।

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

faulty_header.h

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

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

broken_header.h

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

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

seldom_correct.h

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

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

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

सारांश

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

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

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

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

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

देर मेजर जोड़

कोड डुप्लिकेशन से बचें

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

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

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 :

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 का उपयोग करता है।

परिवर्तनीय प्रारंभिकरण

दिखाए गए इस योजना के साथ समस्या यह है कि यह वैश्विक चर के प्रारंभिकरण के लिए प्रदान नहीं करता है। मैक्रोज़ के लिए सी 99 या सी 11 और परिवर्तनीय तर्क सूचियों के साथ, आप प्रारंभिकरण का समर्थन करने के लिए एक मैक्रो को भी परिभाषित कर सकते हैं। (सी 8 9 के साथ और मैक्रोज़ में परिवर्तनीय तर्क सूचियों के लिए कोई समर्थन नहीं है, मनमाने ढंग से लंबे प्रारंभकर्ताओं को संभालने का कोई आसान तरीका नहीं है।)

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 ब्लॉक की सामग्री को #else , डेनिस Kniazhev द्वारा पहचाना बग फिक्सिंग

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

जाहिर है, oddball संरचना के लिए कोड वह नहीं है जिसे आप सामान्य रूप से लिखते हैं, लेकिन यह बिंदु को दर्शाता है। INITIALIZER के दूसरे आमंत्रण के लिए पहला तर्क { 41 और शेष तर्क (इस उदाहरण में एकवचन) 43 } । सी 99 के बिना या मैक्रोज़ के लिए परिवर्तनीय तर्क सूचियों के लिए समान समर्थन के बिना, प्रारंभकर्ताओं को अल्पविराम शामिल करने की आवश्यकता होती है जो बहुत ही समस्याग्रस्त हैं।

डेनिस Kniazhev प्रति सही हेडर file3b.h शामिल ( file3b.h बजाय)

अगली दो फाइलें prog4 लिए स्रोत को पूरा 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 , prog4 का उपयोग करता है।

शीर्षलेख गार्ड

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

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

हेडर को अप्रत्यक्ष रूप से दो बार शामिल किया जा सकता है। उदाहरण के लिए, अगर file4b.h में एक प्रकार की परिभाषा के लिए file4b.h शामिल है जो दिखाया नहीं गया है, और file1b.c को हेडर file4b.h और file3b.h दोनों का उपयोग करने की आवश्यकता है, तो आपके पास हल करने के लिए कुछ और कठिन समस्याएं हैं। स्पष्ट रूप से, आप केवल file4b.h को शामिल करने के लिए हेडर सूची संशोधित कर सकते हैं। हालांकि, हो सकता है कि आप आंतरिक निर्भरताओं से अवगत न हों - और कोड आदर्श रूप से काम करना जारी रखे।

इसके अलावा, यह मुश्किल हो जाना शुरू हो जाता है क्योंकि आप परिभाषा उत्पन्न करने के लिए file3b.h समेत file3b.h file4b.h शामिल कर सकते हैं, लेकिन file3b.h पर सामान्य हेडर गार्ड हेडर को फिर से शामिल करने से रोक देगा।

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

परिवर्तनीय परिभाषाओं के साथ एकाधिक समावेशन

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

  • external.h मैक्रो परिभाषाओं आदि के लिए external.h .h
  • 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

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is invoked, it redefines the macros EXTERN, INITIALIZE
** based on whether macro DEFINE_VARIABLES is currently defined.
*/
#undef EXTERN
#undef INITIALIZE

#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 prog6 लिए स्रोत (मुख्य कार्यक्रम प्रदान करती है) को prog5 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 कहते हैं) द्वारा परिभाषित किया गया है जो चर को परिभाषित करता है तो आप केवल एक समस्या में भाग 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 साथ किसी भी परिभाषित आमंत्रण को #undef :

#define DEFINE_VARIABLES
#include "file2c.h"
#undef DEFINE_VARIABLES

स्रोत कोड में (इसलिए हेडर कभी DEFINE_VARIABLES के मान को परिवर्तित नहीं करते हैं), तो आपको साफ़ होना चाहिए। अतिरिक्त लाइन लिखना याद रखना सिर्फ एक उपद्रव है। एक विकल्प हो सकता है:

#define HEADER_DEFINING_VARIABLES "file2c.h"
#include "externdef.h"

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #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 हो रहा है, लेकिन सुरक्षित लगता है ( file2d.h का उपयोग कर, #undef DEFINE_VARIABLES में #undef DEFINE_VARIABLES साथ)।

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 लिए स्रोत को पूरा 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 , prog8 का उपयोग करता है।
  • prog9 prog8.c , file8c.c , prog9 का उपयोग करता है।

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

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

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

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

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


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


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

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

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


First off, the extern keyword is not used for defining a variable; rather it is used for declaring a variable. I can say extern is a storage class, not a data type.

extern is used to let other C files or external components know this variable is already defined somewhere. Example: if you are building a library, no need to define global variable mandatorily somewhere in library itself. The library will be compiled directly, but while linking the file, it checks for the definition.







extern