Mənbə faylları arasında dəyişən dəyişənlərə extern necə istifadə olunur?

Bildiyimizdə qlobal dəyişənlərin bəzən extern malik olduğunu bilirəm. extern dəyişən nədir? Bəyanat nədir? Onun əhatəsi nədir?

Bu, mənbə fayllarında dəyişənlərin mübadiləsi ilə bağlıdır, lakin necə işləyir? Xarici nereden istifadə edə bilərəm?

867
16 сент. set şilpa 16 sm . 2009-09-16 17:08 '09 at 17:08 'da 2009-09-16 17:08
@ 16 cavab

extern istifadə yalnız yaratdığınız proqram birbaşa əlaqəli olan bir neçə mənbə faylından file1.c , məsələn, müəyyən edilmiş dəyişənlərin bir hissəsi, məsələn, mənbə faylının file1.c də müəyyənləşdirildikdə file2.c kimi digər mənbə fayllarında göstərilməlidir. file2.c .

Bir dəyişən təyin etmək və bir dəyişən elan etmək arasındakı fərqi anlamaq vacibdir:

  • Derivəyə bir dəyişən var (və onun növüdür) deyildiyi zaman dəyişən elan edilir; bu nöqtədə dəyişən üçün saxlama ayırmır.
  • Derleyici bir dəyişən üçün saxlama ayırdığında bir dəyişən müəyyən edilir .

Bir dəfə dəyişə bilər (bir dəfə olsa da); Siz müəyyən bir sahədə yalnız bir dəfə müəyyən edə bilərsiniz. Dəyişən tərif də bir bəyanatdır, lakin bütün dəyişən bəyannamələr anlayışlar deyil.

Qlobal dəyişənləri bəyan etmək və müəyyən etmək üçün ən yaxşı yol

Qlobal dəyişənləri bəyan etmək və müəyyən etmək üçün təmiz, etibarlı bir yol extern dəyişən bəyannaməsini ehtiva edən bir başlıq faylını istifadə etməkdir.

Başlıq, dəyişənə istinad edən bir qaynaq faylına və bütün mənbə fayllarına daxil edilir. Hər bir proqram üçün bir parametr (və yalnız bir qaynaq faylı) bir dəyişən müəyyən edir. Eynilə, bir üstbilgi faylı (və yalnız bir başlıq faylı) bir dəyişən elan etməlidir. Başlıq faylı vacibdir; müstəqil TU (mənbə vahidləri) arasında cross-validasiya etməyə imkan verir və uyğunluq təmin edir.

Bunun başqa yolları olsa da, bu üsul sadə və etibarlıdır. Bu file3.h , file1.cfile2.c ilə nümayiş olunur:

file3.h

 extern int global_variable;  

file1.c

 #include "file3.h"  #include "prog1.h"   int global_variable = 37;  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++); } 

Bu, qlobal dəyişənləri bəyan etmək və müəyyən etmək üçün ən yaxşı yoldur.


Aşağıdakı iki fayl prog1 üçün qaynağı prog1 :

Yuxarıda göstərilən proqramlarda funksiyalar göstərilir, buna görə funksiyaların bəyanatları onlara daxil edilir. Həm C99, həm də C11 funksiyaları istifadə edilməzdən əvvəl elan edilməli və ya müəyyən edilməlidir (C90 yaxşı səbəbdəndir). Başlıqlarda funksiyanı elan etmədən extern açar sözünü istifadə edirəm - başlıqlarda dəyişən bəyannamələrdən əvvəl extern ilə uyğunlaşmaq üçün. Bir çox insanlar funksiyalar bəyannamələrindən əvvəl extern istifadə etməməyi üstün edir; kompilyator özü də maraqlandırmır - ən sonunda mənbə faylında razılaşdığınız müddətdə heç bir nəticə vermirəm.

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.hprog1.h .

prog1.mk faylı yalnız prog1 üçün prog1 . Millenniumun növündən bəri istehsal edilən məhsulun ən çox versiyaları ilə işləyəcək. GNU Make ilə əlaqəli deyil.

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} 

Metodik təkliflər

Qaydalar yalnız ekspertlər tərəfindən pozulmalıdır və yalnız əsas səbəbi ilə:

  • Başlıq faylı yalnız extern dəyişənlərin bəyanatlarını ehtiva edir - heç static və ya qeyri-dəqiqləşdirilmiş dəyişən anlayışlar.
  • Hər hansı bir dəyişən üçün yalnız bir başlıq fayl (SPOT - Həqiqətin tək nöqtəsi) elan edilir.
  • Mənbə faylında extern dəyişən bəyannamələr yoxdur - mənbə faylları həmişə onları elan edən bir başlıq (yalnız) daxildir.
  • Hər hansı bir dəyişən üçün, tam bir qaynaq faylı bir dəyişən müəyyən edir, tercihen onu başlatır. (Proqramda xüsusi bir qlobal dəyişənin yalnız bir başlanğıc edilmiş tərifi ola biləcəyi üçün sıfıra açıq şəkildə başlamaq lazım deyil, bu zərərli deyil və faydalı ola bilər).
  • Dəyişənləri müəyyən edən qaynaq faylı, həmçinin tərif və bəyannamənin ardıcıl olduğunu təmin etmək üçün başlığı ehtiva edir.
  • Funksiya extern istifadə edərək heç bir dəyişən elan etməməlidir.
  • Mümkün olduğunda qlobal dəyişənlərdən çəkin, yerinə funksiyaları istifadə edin.

Mənbə kodu və bu cavabın mətni src / so-0143-3204 alt qovşağında GitHub üzrə SOQ (on123.ru Sorğuları) deposunda mövcuddur.

Təcrübəli C proqramçı deyilsinizsə, buradan oxumağı dayandırmaq (və ehtimal ki) olmalıdır.

Qlobal dəyişənləri müəyyən etmək üçün yaxşı bir yol deyil.

Bəzi (həqiqətən çoxlu) C kompilyatorları ilə, "ümumi" dəyişən tərifdən xilas ola bilərsiniz. "Ortaq" Burada Fortran'da istifadə edilən metoddan qaynaqlı fayllar arasında dəyişənləri dəyişdirmək üçün istifadə edilə bilər. Burada baş verənlər, bir neçə faylın hər birində dəyişən bir ilkin tərifi var. Birdən çox fayl başlanğıc edilmiş bir təsəvvür təmin etmirsə, müxtəlif fayllar eyni dəyişən tərifini bölüşdürür:

file10.c

 #include "prog2.h" int i;  void inc(void) { i++; } 

file11.c

 #include "prog2.h" int i;  void dec(void) { i--; } 

file12.c

 #include "prog2.h" #include <stdio.h> int i = 9;  void put(void) { printf("i = %d\n", i); } 

Bu metod C standartı məktubuna və "bir tərif qayda" ilə uyğun gəlmir - bu, rəsmi olaraq müəyyən edilməyən davranışdır:

J.2 Qeyri-müəyyən davranış

Xarici keçid olan identifikator istifadə olunur, lakin proqramda identifikator üçün yalnız bir xarici tərif yoxdur və ya identifikator istifadə edilmir və identifikator üçün bir çox xarici təriflər var (6.9).

§6.9 Xarici təriflər ¶5

Xarici bir təsəvvür, bir funksiyanı (bir inline təsvirdən başqa) və ya bir obyektin bir təsviri olan xarici bəyannaməsidir. Xarici əlaqəyə _Alignof bir ifadədə istifadə edildikdə ( _Alignof operand hissəsi və ya nəticəsi tamsayı sabitdir) istisna olmaqla, bütün proqramda bir yerdə identifikator üçün tam bir xarici tərif olmalıdır; əks halda birdən artıq olmamalıdır. 161)

161) Beləliklə, bir ifadə ilə xarici əlaqəsi ilə müəyyən edilən bir identifikator istifadə edilmirsə, bunun üçün heç bir xarici tərif yoxdur.

Bununla yanaşı, C standartı həmçinin, informasiya tətbiqində J'yi ümumi uzantılardan biri olaraq siyahıya alır.

J.5.11 Bir çox xarici təriflər

Bir obyektin identifikatoru üçün, extern açarının dəqiq istifadə edilməsi ilə bir neçə xarici təriflər ola bilər; anlayışlar uyğunsuzdursa və ya birdən çox başlansa, davranış müəyyən edilmir (6.9.2).

Bu metod həmişə dəstəklənməmiş olduğundan, xüsusən kodunuz portativ olmalıdırsa, onu istifadə etməməkdən daha yaxşıdır. Bu texnikadan istifadə edərək, istənilməyən dövriyyəyə də rast gəlinir. Dosyalardan biri double olaraq deyil, int kimi deyilsə, C tipli təhlükəsiz bağlayıcılar ehtimal ki, uyğunsuzluğu görməyəcəkdir. 64-bit intint bir maşında olsanız, hətta xəbərdarlıq etməyəcəksiniz; 32-bit int və 64-bitli int bir maşında, ehtimal ki, müxtəlif ölçülərdə xəbərdarlıq alacaqsınız - linker ən böyük ölçüsü istifadə edəcək, Fortran proqramı kimi, hər hansı bir ümumi blokun böyük ölçüsünü alacaq.


Aşağıdakı iki fayl prog2 üçün qaynağı 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 .

Xəbərdarlıq

Burada şərhlərdə qeyd edildiyi kimi və oxşar bir suala cavab verdiyim kimi, qlobal bir dəyişiklik üçün bir neçə təsəvvür istifadə edərək, müəyyən bir davranışa gətirib çıxarır (J.2, §6.9). Bu "bir şey ola bilər" deməkdir. Başlaya biləcək şeylərdən biri proqramın gözlənildiyi kimi davranmasıdır; və J.5.11 belə bir şey deyir: "Sizə layiq olduğunuzdan daha çox şanslı ola bilərsiniz." Ancaq bir xarici dəyişənin bir neçə tərifinə əsaslanan bir proqram - "extern" sözü ilə və ya olmadan - ciddi bir proqram deyil və hər yerdə işləmək üçün təmin edilmir. Eşdeğer: Bu, görünen və ya görünmeyen bir səhv ehtiva edir.

Qaydaları pozmaq

Əlbəttə ki, bu tövsiyələri aradan qaldırmaq üçün bir çox yollar var. Bəzən tövsiyələri pozmaq üçün yaxşı bir səbəb ola bilər, lakin bu cür vəziyyətlər son dərəcə qeyri-adi haldır.

faulty_header.h

 int some_var;  

Qeyd 1: əgər üstbilgi extern açar extern bir dəyişən müəyyən edərsə, üstbilgi olan hər bir fayl dəyişənin ilkin təsvirini yaradır. Daha əvvəl qeyd edildiyi kimi, bu tez-tez işləyəcək, lakin C standartı işləyəcəkdir.

broken_header.h

 int some_var = 13;  

Qeyd 2: əgər başlıq bir dəyişən müəyyən edərsə və ilkləşdirirsə, bu proqramda yalnız bir qaynaq fayl başlığı istifadə edə bilər. Başlıqlar əsasən məlumat mübadiləsi üçün olduğundan, bir dəfə istifadə edilə bilən bir şey yaratmaq bir az daimdir.

seldom_correct.h

 static int hidden_global = 3;  

Qeyd 3: başlıq statik dəyişən (başlatma ilə və ya olmadan) təyin edərsə, hər bir mənbə faylı "qlobal" dəyişən özünün xüsusi versiyası ilə başa çatır.

Məsələn, bir dəyişən kompleks bir sıra varsa, bu həddindən artıq kod çoğaltmasına səbəb ola bilər. Bəzən müəyyən bir təsirə nail olmaq üçün bu həssas bir yol ola bilər, ancaq bu çox qeyri-adi.


Xülasə

Birinci mən göstərdiyim mövzu texnikasını istifadə edin. Bu etibarlı və hər yerdə çalışır. Xüsusilə, global_variable elan edən başlıq istifadə olunan hər bir faylda, o cümlədən onu müəyyən edənlərdən ibarətdir. Bu, hər şeyin özünəməxsusluğunu təmin edir.

Bənzər problemlər funksiyaların bəyan edilməsi və təsviri ilə yaranır - oxşar qaydalar tətbiq olunur. Ancaq sual yalnız dəyişənlərə aiddir, ona görə də cavabı dəyişənlərə verdilər.

Orijinal cavabın sonu

Təcrübəli C proqramçı deyilsinizsə, ehtimal ki burada oxu dayandırmalısınız.


Daha sonra əhəmiyyətli əlavə

Kodun çoğaltılmasına yol verməyin

Bəzən (və qanuni olaraq) başlıq və mənbə olan iki senkronlaşdırılmış fayl - burada təsvir edilən "başlıqlarda bəyanatlar, mənbədə müəyyənləşdirmə" mexanizmi ilə bağlı yaranan problemlərdən biri. Bu, adətən, başlığı dəyişənləri elan edərək, adətən, ikiqat yükə xidmət etməsi üçün makroun istifadə oluna biləcəyi müşahidə ilə müşayiət olunur, ancaq başlığı daxilində müəyyən bir makro müəyyən edilərkən, dəyişənləri müəyyən edir.

Başqa bir problem, dəyişənlərin bir neçə "əsas proqram" hər birində müəyyən edilməlidir. Bu adətən saxta qayğı; Dəyişənləri müəyyən etmək və proqramların hər biri ilə yaradılan bir obyekt faylını birləşdirmək üçün sadəcə mənbə C faylını daxil edə bilərsiniz.

Tipik bir sxem, file3.h göstərilən qaynaqlı qlobal dəyişəndən istifadə edərək file3.h :

file3a.h

 #ifdef DEFINE_VARIABLES #define EXTERN  #else #define EXTERN extern #endif  EXTERN int global_variable; 

file1a.c

 #define DEFINE_VARIABLES #include "file3a.h"  #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++); } 

Aşağıdakı iki fayl prog3 üçün qaynağı 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 .

Dəyişən başlatma

Bu sxem ilə göstərilən problem, göstərildiyi kimi, qlobal dəyişənin başlanmasına imkan vermir. C99 və ya C11 və dəyişən arqument siyahıları ilə makrolar üçün, başlatma dəstəkləmək üçün makroları da təyin edə bilərsiniz. (C89 ilə və makrolarda dəyişən arqument siyahısının dəstəyi olmadan, şərti olaraq uzun inisliyorların işlənməsi üçün asan bir yol yoxdur.)

file3b.h

 #ifdef DEFINE_VARIABLES #define EXTERN  #define INITIALIZER(...) = __VA_ARGS__ #else #define EXTERN extern #define INITIALIZER(...)  #endif  EXTERN int global_variable INITIALIZER(37); EXTERN struct { int a; int b; } oddball_struct INITIALIZER({ 41, 43 }); 

Denis Knyazev tərəfindən aşkar edilən səhvlərin düzəldilməsi və #else tərs məzmunu

file1b.c

 #define DEFINE_VARIABLES #include "file3b.h"  #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; } 

Aydındır ki, oddball strukturunun kodu çox vaxt yazdığınız deyil, ancaq özünü göstərir. İkinci INITIALIZER zənginin ilk arqumenti { 41 və qalan arqument (bu nümunədə yeganə biri) INITIALIZER . Makrolar üçün argument dəyişən siyahıları üçün C99 və ya oxşar dəstək olmadan virgül ehtiva edən başlanğıclar çox problemlidir.

Denis Knyazev tərəfindən file3b.h ( file3b.h yerine) fileba.h Sabit başlıq


Aşağıdakı iki fayl prog4 üçün qaynağı 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 .

Mövzular

Hər hansı başlıq təkrarlanmasından qorunmalıdır, belə ki, tip təsvirləri (sayım, struktur və ya birləşmələrin növləri və ya ümumiyyətlə yazma) problem yarada bilməz. Standart metod başlıq gövdəsini başlıq qorunmasında saxlayır, məsələn:

 #ifndef FILE3B_H_INCLUDED #define FILE3B_H_INCLUDED ...contents of header... #endif  

Adı iki dəfə dolayı yolla daxil edilə bilər. Məsələn file4b.h , file3b.h göstərməyən bir növü müəyyən etmək üçün və file1b.c file4b.hfile3b.h istifadə etməlidirsə, daha da çətin problemləriniz var. Şübhəsiz, yalnız file4b.h etmək üçün başlığı dəyişə bilərsiniz. Ancaq daxili içərisində xəbərdarlıq ola bilməz və idealı olaraq kodu yaxşı işləməlidir.

Əlavə olaraq, file3b.h üçün file4b.h edə bilərsiniz, çünki file4b.h əvvəl, lakin file3b.h müntəzəm file3b.h başlıqları başlığı yenidən aktivləşdirməyə mane olur.

Beləliklə, bəyanat file3b.h üçün birdən çox dəfə və bir neçə dəfə anlayışlar üçün əlavə file3b.h , lakin bir tərcümə vahidi kimi tələb oluna bilər (TU, mənbə faylının və istifadə olunan başlıqların birləşməsidir)

Değişken tanımlarla birden çox daxil etmə

Ancaq bu çox asılı olmayan məhdudiyyətlərə görə edilə bilər. Yeni bir fayl adını təqdim edirik:

  • EXTERN makroları üçün xarici.h və s.
  • file1c.h tipləri müəyyən etmək üçün (xüsusilə, struct oddball , oddball_struct növü).
  • file2c.h qlobal dəyişənləri təyin etmək və ya elan etmək.
  • Qlobal dəyişənləri müəyyən edən file3c.c .
  • file4c.c qlobal dəyişənləri istifadə edən file4c.c .
  • file5c.c qlobal dəyişənləri bəyan edə və sonra təyin edə biləcəyinizi göstərir.
  • file6c.c qlobal dəyişənləri bəyan etmək üçün müəyyən etmək və sonra (cəhd) file6c.c biləcəyinizi göstərir.

Bu nümunələrdə file5c.cfile6c.c bir neçə dəfə başlıq file2c.h bir neçə dəfə daxildir, lakin bu mexanizmin işləməyini göstərmək üçün ən asan yoldur. Bu, başlığı dolayı yolla iki dəfə daxil edərsə, o da təhlükəsiz olardı.

Bunun üçün məhdudiyyətlər:

  1. Qlobal dəyişənləri müəyyən edən və ya bəyan edən bir başlıq hər hansı bir növü özü ilə müəyyən edə bilməz.
  2. Değişkenleri belirlemesi gereken üstbilgiyi hemen eklemeden hemen önce makro DEFINE_VARIABLES tanımlayabilirsiniz.
  3. Değişkenleri tanımlayan ya da bildiren başlık stilize edilmiş içeriğe malikdir.

xarici.h

  #undef EXTERN #undef INITIALIZE #ifdef DEFINE_VARIABLES #define EXTERN  #define INITIALIZE(...) = __VA_ARGS__ #else #define EXTERN extern #define INITIALIZE(...)  #endif  

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  

file2c.h

  #if defined(DEFINE_VARIABLES)  !defined(FILE2C_H_DEFINITIONS) #undef FILE2C_H_INCLUDED #endif #ifndef FILE2C_H_INCLUDED #define FILE2C_H_INCLUDED #include "external.h"  #include "file1c.h"  #if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)  EXTERN int global_variable INITIALIZE(37); EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 }); #endif   #ifdef DEFINE_VARIABLES #define FILE2C_H_DEFINITIONS #endif  #endif  

file3c.c

 #define DEFINE_VARIABLES #include "file2c.h"  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"  #define DEFINE_VARIABLES #include "file2c.h"  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"  #include "file2c.h"  int increment(void) { return global_variable++; } int oddball_value(void) { return oddball_struct.a + oddball_struct.b; } 

Aşağıdakı mənbə fayl prog5 , prog6prog7 üçün qaynaq kodunu tamamlayır (əsas proqram təmin edir):

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 .

Bu sxem bir çox problemdən qaçınır. file2c.h ( file7c.h ) değişkenleri tanımlayan file7c.h bir üstbilgi ( file7c.h , file7c.h ) file7c.h . "Bunu etməmək" kimi asan deyil.

Siz file2c.h file2d.h file2c.h file2c.h file2d.h qismən həll edə bilərsiniz:

file2d.h

  #if defined(DEFINE_VARIABLES)  !defined(FILE2D_H_DEFINITIONS) #undef FILE2D_H_INCLUDED #endif #ifndef FILE2D_H_INCLUDED #define FILE2D_H_INCLUDED #include "external.h"  #include "file1c.h"  #if !defined(DEFINE_VARIABLES) || !defined(FILE2D_H_DEFINITIONS)  EXTERN int global_variable INITIALIZE(37); EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 }); #endif   #ifdef DEFINE_VARIABLES #define FILE2D_H_DEFINITIONS #undef DEFINE_VARIABLES #endif  #endif  

Проблема становится "должен ли заголовок включать #undef DEFINE_VARIABLES ?" Если вы опустите это из заголовка и оберните любой определяющий вызов #define и #undef :

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

в исходном коде (поэтому заголовки никогда не изменяют значение DEFINE_VARIABLES ), тогда вы должны быть чистыми. Это просто неприятность, которую нужно помнить, чтобы написать дополнительную строку. Alternativ ola bilər:

 #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  

Это становится немного запутанным, но кажется безопасным (с использованием file2d.h , без #undef DEFINE_VARIABLES в file2d.h ).

file7c.c

  #include "file2d.h"  #define HEADER_DEFINING_VARIABLES "file2d.h" #include "externdef.h"  #include "file2d.h"  #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

  #if defined(DEFINE_VARIABLES)  !defined(FILE8C_H_DEFINITIONS) #undef FILE8C_H_INCLUDED #endif #ifndef FILE8C_H_INCLUDED #define FILE8C_H_INCLUDED #include "external.h"  #include "file2d.h"  #if !defined(DEFINE_VARIABLES) || !defined(FILE8C_H_DEFINITIONS)  EXTERN struct oddball another INITIALIZE({ 14, 34 }); #endif   #ifdef DEFINE_VARIABLES #define FILE8C_H_DEFINITIONS #endif  #endif  

file8c.c

  #define HEADER_DEFINING_VARIABLES "file2d.h" #include "externdef.h"  #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 .

Тем не менее, проблемы относительно маловероятны на практике, особенно если вы примете стандартный совет для

Избегайте глобальных переменных


Означает ли это изложение что-нибудь?

Исповедь: схема, "исключающая дублированный код", описанная здесь, была разработана, потому что проблема затрагивает некоторый код, над которым я работаю (но не владею), и вызывает беспокойство по поводу схемы, изложенной в первой части ответа. Однако исходная схема оставляет вам только два места для изменения, чтобы синхронизировать определения переменных и декларации, что является большим шагом вперед по сравнению с объявлениями exernal переменных, разбросанными по всей базе кода (что действительно имеет значение, когда в нем всего тысячи файлов), Однако код в файлах с именами fileNc.[ch] (плюс external.h и externdef.h ) показывает, что его можно externdef.h работать. Очевидно, что создать сценарий генератора заголовков не составит труда, чтобы дать вам стандартизованный шаблон для переменной, определяющей и объявляющий заголовочный файл.

NB Это игрушечные программы с едва достаточным количеством кода, чтобы сделать их немного интересными. В примерах, которые можно было бы удалить, есть повторение, но это не должно упрощать педагогическое объяснение. (Например: разница между prog5.c и prog8.c - это имя одного из включенных заголовков. Можно было бы реорганизовать код так, чтобы функция main() не повторялась, но она скрывала бы больше чем это показало.)

1560
ответ дан Jonathan Leffler 16 сент. '09 в 17:37 2009-09-16 17:37

An extern variable - это объявление (благодаря sbi для коррекции) переменной, которая определена в другой единицы перевода. Это означает, что хранилище для переменной выделяется в другом файле.

Скажем, у вас есть два .c файла test1.c и test2.c . Если вы определяете глобальную переменную int test1_var; в test1.c и хотите получить доступ к этой переменной в test2.c , вы должны использовать extern int test1_var; в test2.c .

border=0

Полный образец:

 $ 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 
112
ответ дан Johannes Weiss 16 сент. '09 в 17:12 2009-09-16 17:12

Extern - это ключевое слово, которое вы используете, чтобы объявить, что сама переменная находится в другой единицы перевода.

Таким образом, вы можете решить использовать переменную в блоке трансляции, а затем получить доступ к ней из другой, а затем во втором вы объявите ее как extern, и символ будет разрешен компоновщиком.

Если вы не объявите его как extern, вы получите две переменные с именем одинаковые, но не связанные вообще, и ошибку нескольких определений переменной.

36
ответ дан Arkaitz Jimenez 16 сент. '09 в 17:11 2009-09-16 17:11

Мне нравится думать о переменной extern в качестве обещания, которое вы делаете в компиляторе.

При встрече с extern компилятор может узнать только его тип, а не там, где он "живет", поэтому он не может разрешить ссылку.

Вы говорите: "Поверьте мне. В момент ссылки эта ссылка будет разрешимой".

24
ответ дан Buggieboy 16 сент. '09 в 17:50 2009-09-16 17:50

extern сообщает компилятору доверять вам, что память для этой переменной объявлена ​​в другом месте, поэтому она не пытается выделить/проверить память.

Поэтому вы можете скомпилировать файл, который ссылается на extern, но вы не можете связать, если эта память не была объявлена ​​где-то.

Полезно для глобальных переменных и библиотек, но опасно, потому что компоновщик не печатает проверку.

18
ответ дан BenB 16 сент. '09 в 17:18 2009-09-16 17:18

Добавление extern превращает определение переменной в объявление переменной. См. этот поток относительно разницы между объявлением и определением.

15
ответ дан sbi 16 сент. '09 в 17:16 2009-09-16 17:16

Правильная интерпретация extern заключается в том, что вы сообщаете что-то компилятору. Вы сообщаете компилятору, что, несмотря на то, что он не присутствует прямо сейчас, объявленная переменная каким-то образом будет найдена компоновщиком (как правило, в другом объекте (файле)). Компилятор тогда будет счастливым парнем, чтобы найти все и собрать его вместе, были ли у вас какие-то объявления extern или нет.

11
ответ дан Alex Lockwood 21 июня '12 в 2:43 2012-06-21 02:43

В C переменная внутри файла say example.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
ответ дан Phoenix225 02 июля '12 в 12:11 2012-07-02 12:11

ключевое слово extern используется с переменной для ее идентификации как глобальной переменной.

Он также означает, что вы можете использовать переменную, объявленную с использованием extern ключевое слово в любом файле, хотя оно объявлено/определено в другом файле.

7
ответ дан Anup 20 авг. '12 в 13:19 2012-08-20 13:19

Реализация 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 

В главе "Справочная таблица" Символьная таблица ELF "в разделе" Система V ABI "объясняется:

SHN_UNDEF Этот индекс таблицы разделов означает, что символ не определен. Когда редактор ссылок объединяет этот объектный файл с другим, который определяет указанный символ, этот файл ссылается на символ, который будет связан с фактическим определением.

который в основном является поведением, которое дает стандарт C extern переменным.

С этого момента задача компоновщика заключается в создании окончательной программы, но extern информация уже извлечена из исходного кода в объектный файл.

Проверено на GCC 4.8.

6
ответ дан Ciro Santilli 新疆改造中心 六四事件 法轮功 29 мая '15 в 10:34 2015-05-29 10:34

extern просто означает, что переменная определена в другом месте (например, в другом файле).

5
ответ дан Geremia 27 янв. '16 в 22:47 2016-01-27 22:47

extern позволяет одному модулю вашей программы получить доступ к глобальной переменной или функции, объявленной в другом модуле вашей программы. Обычно у вас есть внешние переменные, объявленные в файлах заголовков.

Если вы не хотите, чтобы программа обращалась к вашим переменным или функциям, вы используете static , который сообщает компилятору, что эту переменную или функцию нельзя использовать вне этого модуля.

5
ответ дан loganaayahee 03 окт. '12 в 7:58 2012-10-03 07:58

Во-первых, ключевое слово extern не используется для определения переменной; скорее он используется для объявления переменной. Я могу сказать, что extern - это класс хранения, а не тип данных.

extern используется, чтобы другие C файлы или внешние компоненты знали, что эта переменная уже определена где-то. Пример: если вы создаете библиотеку, не нужно обязательно определять глобальную переменную где-то в самой библиотеке. Библиотека будет скомпилирована напрямую, но при связывании файла она проверяет определение.

4
ответ дан user1270846 09 авг. '12 в 12:21 2012-08-09 12:21

extern используется, поэтому один файл first.c может иметь полный доступ к глобальному параметру в другом файле second.c .

extern может быть объявлен в файле first.c или в любом из файлов заголовков first.c .

3
ответ дан shoham 01 сент. '14 в 10:35 2014-09-01 10:35

С xc8 вы должны быть осторожны, объявляя переменную одного и того же типа в каждом файле, чтобы вы могли ошибочно объявить что-то типа int в одном файле и выражение char в другом. Это может привести к повреждению переменных.

Эта проблема была элегантно решена на форуме по микрочипам около 15 лет назад. * * См. "Http: www.htsoft.com"//"forum/all/showflat.php/Cat/0/Number/18766/an/0/page/0 # 18766"

Но эта ссылка, похоже, больше не работает...

Поэтому я быстро попытаюсь объяснить это; сделать файл с именем global.h.

В нем заявляют следующее

 #ifdef MAIN_C #define GLOBAL  #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 при использовании компилятора, который позволяет вам объявить переменную в одном файле, а затем объявить ее extern как другой тип в другом файле. Проблемы, связанные с этим, заключаются в том, что если вы скажете, что в другом файле объявлен testing_mode как int, то он будет думать, что это 16-битное var, и перезапишет какую-то другую часть ram, что может повредить другую переменную. Сложно отлаживать!

1
ответ дан user50619 09 окт. '18 в 13:01 2018-10-09 13:01
  declare | define | initialize | ---------------------------------- extern int a; yes no no ------------- int a = 2019; yes yes yes ------------- int a; yes yes no ------------- 

Объявление не будет выделять память (переменная должна быть определена для выделения памяти), но определение будет. Это просто еще один простой взгляд на ключевое слово extern, поскольку другие ответы действительно хороши.

1
ответ дан Lucian Nut 09 янв. '19 в 23:50 2019-01-09 23:50

Другие вопросы по меткам или Задайте вопрос