Kaynak dosyaları arasında değişkenleri paylaşmak için extern'i nasıl kullanabilirim?


987

C'deki global değişkenlerin bazen externanahtar kelimeye sahip olduğunu biliyorum . Bir nedir externdeğişken? Beyan nasıl bir şey? Kapsamı nedir?

Bu, değişkenlerin kaynak dosyalar arasında paylaşılmasıyla ilgilidir, ancak bu tam olarak nasıl çalışır? Nerede kullanırım extern?

Yanıtlar:


1751

Kullanılması externprogram olduğunuz binadaki bazı değişkenlerin kaynak dosyası, örneğin, tanımlanmış birbirine bağlı birden kaynak dosyaları oluşur yalnızca alaka olduğu file1.cgibi, diğer kaynak dosyaları başvurulan gerek file2.c.

Bir değişkeni tanımlamak ve bir değişkeni bildirmek arasındaki farkı anlamak önemlidir :

  • Bir değişken olduğu bildirilen derleyici değişken var olduğu bilgi zaman (ve bu da tip); o noktada değişken için depolamayı ayırmaz.

  • Bir değişken, derleyici değişken için depolama alanı ayırdığında tanımlanır .

Bir değişkeni birden çok kez bildirebilirsiniz (bir kez yeterli olsa da); belirli bir kapsam dahilinde yalnızca bir kez tanımlayabilirsiniz. Değişken tanımı da bir bildiridir, ancak tüm değişken bildirimleri tanım değildir.

Global değişkenleri tanımlamanın ve tanımlamanın en iyi yolu

Global değişkenleri tanımlamanın ve tanımlamanın temiz ve güvenilir yolu, değişkenin bir extern bildirimini içeren bir başlık dosyası kullanmaktır .

Üstbilgi, değişkeni tanımlayan bir kaynak dosya ve değişkeni referans alan tüm kaynak dosyalar tarafından eklenir. Her program için bir kaynak dosya (ve yalnızca bir kaynak dosya) değişkeni tanımlar. Benzer şekilde, bir başlık dosyası (ve yalnızca bir başlık dosyası) değişkeni bildirmelidir. Başlık dosyası çok önemlidir; bağımsız TU'lar (çeviri birimleri - düşünme kaynak dosyaları) arasında çapraz kontrol sağlar ve tutarlılığı sağlar.

Bunu yapmanın başka yolları olsa da, bu yöntem basit ve güvenilirdir. Bu ile gösterilmiştir file3.h, file1.cve 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++);
}

Global değişkenleri tanımlamanın ve tanımlamanın en iyi yolu budur.


Sonraki iki dosya kaynağı tamamlar prog1:

Gösterilen tam programlar işlevler kullanır, bu nedenle işlev bildirimleri sızmıştır. Hem C99 hem de C11, işlevlerin kullanılmadan önce bildirilmesini veya tanımlanmasını gerektirir (oysa C90 iyi nedenlerle). externÜstbilgideki externdeğişken bildirimlerinin önündeki eşleşmeleri için, üstbilgideki işlev bildirimlerinin önündeki anahtar sözcüğü kullanıyorum . Birçok kişi externişlev bildirimlerinin önünde kullanılmamayı tercih eder ; derleyici umursamıyor - ve nihayetinde, tutarlı olduğunuz sürece, en azından bir kaynak dosyada da.

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;
}
  • prog1kullanımları prog1.c, file1.c, file2.c, file3.hve prog1.h.

Dosya prog1.mksadece bir makefile prog1. makeMilenyumun başlangıcından bu yana üretilen çoğu versiyonla çalışacaktır . Özellikle GNU Make'a bağlı değildir.

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}


Kuralları

Kurallar sadece uzmanlar tarafından ve sadece iyi bir sebeple kırılacak:

  • Üstbilgi dosyası yalnızca externdeğişken bildirimlerini içerir - hiçbir zaman staticveya niteliksiz değişken tanımları.

  • Herhangi bir değişken için yalnızca bir başlık dosyası bunu bildirir (SPOT - Tek Doğruluk Noktası).

  • Kaynak dosya hiçbir zaman externdeğişken bildirimi içermez - kaynak dosyalar her zaman onları bildiren (tek) üstbilgiyi içerir.

  • Belirli bir değişken için, tam olarak bir kaynak dosyası değişkeni tanımlar, tercihen onu başlatır. (Açık bir şekilde sıfıra başlatılmasına gerek olmamasına rağmen, hiçbir zarar vermez ve iyi olabilir, çünkü bir programda belirli bir genel değişkenin yalnızca bir başlatılmış tanımı olabilir).

  • Değişkeni tanımlayan kaynak dosya, tanımın ve bildirimin tutarlı olmasını sağlamak için başlığı da içerir.

  • Bir fonksiyonun asla kullanarak bir değişken bildirmesi gerekmez extern.

  • Mümkün olduğunca global değişkenlerden kaçının - bunun yerine işlevleri kullanın.

Bu yanıtın kaynak kodu ve metni , src / so-0143-3204 alt dizinindeki GitHub'daki SOQ (Yığın Taşması Soruları) veri havuzumda bulunmaktadır .

Deneyimli bir C programcısı değilseniz, burada okumayı bırakabilirsiniz (ve belki de yapmalısınız).

Global değişkenleri tanımlamak için iyi bir yol değil

Bazı (gerçekten de birçok) C derleyicisiyle, bir değişkenin 'ortak' tanımından da kurtulabilirsiniz. Burada 'yaygın', Fortran'da (muhtemelen adlandırılmış) bir COMMON bloğu kullanarak değişkenler arasında kaynak dosyaları paylaşmak için kullanılan bir tekniği ifade eder. Burada olan şey, bir dizi dosyanın her birinin değişkenin belirsiz bir tanımını sağlamasıdır. Birden fazla dosya başlatılmış bir tanım sağlamadığı sürece, çeşitli dosyalar değişkenin ortak tek bir tanımını paylaşır:

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

Bu teknik, C standardının harfine ve 'bir tanım kuralı' na uymaz - resmi olarak tanımlanmamış davranıştır:

J.2 Tanımsız davranış

Harici bağlantılı bir tanımlayıcı kullanılır, ancak programda tanımlayıcı için tam olarak bir harici tanım yoktur veya tanımlayıcı kullanılmaz ve tanımlayıcı için birden fazla harici tanım vardır (6.9).

§6.9 Dış tanımlar ¶5

Bir dış tanımı da bir işlev (bir satır içi tanımı dışında) ya da bir nesnenin bir tanımıdır harici bildirimidir. Dış bağlantı ile bildirilen bir tanımlayıcı bir ifadede kullanılırsa ( sonucu bir tamsayı sabiti olan bir işlecin sizeofveya _Alignofişlecinin bir parçası dışında ), tüm programın bir yerinde tanımlayıcı için tam olarak bir dış tanım bulunmalıdır; aksi halde birden fazla olamaz. 161)

161) Dolayısıyla, bir ifadede harici bağlantı ile bildirilen bir tanımlayıcı kullanılmazsa, bunun için harici bir tanımlamaya gerek yoktur.

Bununla birlikte, C standardı bunu Ek J'de bilgilendirici Ek'te Ortak uzantılardan biri olarak listelemektedir .

J.5.11 Çoklu harici tanımlar

Extern anahtar sözcüğü açıkça kullanılsın veya kullanılmasın, bir nesnenin tanımlayıcısı için birden fazla harici tanım olabilir; tanımlar kabul edilmiyorsa veya birden fazla başlatılırsa, davranış tanımsızdır (6.9.2).

Bu teknik her zaman desteklenmediğinden, özellikle kodunuzun taşınabilir olması gerekiyorsa , kullanmaktan kaçınmak en iyisidir . Bu tekniği kullanarak, kasıtsız tipte çiftçilik ile de sonuçlanabilir.

Dosyalardan biri yukarıda beyan ederse lolarak doubleyerine olarak long, C'nin tip güvensiz bağlayıcıları muhtemelen uyuşmazlığı nokta olmaz. 64 bitlik bir makinedeyseniz longve doublebir uyarı bile almazsınız; 32 bit longve 64 bitlik bir makinede doublemuhtemelen farklı boyutlar hakkında bir uyarı alırsınız - bağlayıcı bir Fortran programının herhangi bir ortak bloğun en büyük boyutunu alacağı gibi en büyük boyutu kullanır.

2020-05-07'de yayımlanan GCC 10.1.0'ın varsayılan derleme seçeneklerini kullanacak şekilde değiştirdiğini -fno-common, yani varsayılanı geçersiz kılmadıkça -fcommon(veya öznitelikleri vb . Kullanmadığınız sürece yukarıdaki kodun artık bağlanmadığı anlamına gelir. bağlantıya bakın).


Sonraki iki dosya kaynağı tamamlar 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();
}
  • prog2kullanımları prog2.c, file10.c, file11.c, file12.c, prog2.h.

Uyarı

Buradaki yorumlarda belirtildiği gibi ve benzer bir soruya cevabımda belirtildiği gibi, küresel bir değişken için çoklu tanımların kullanılması, standardın "her şey olabilir" demenin yolu olan tanımsız davranışlara (J.2; §6.9) yol açar. Olabilecek şeylerden biri, programın beklediğiniz gibi davranmasıdır; ve J.5.11 yaklaşık olarak "hak ettiğinden daha sık şanslı olabilirsin" diyor. Ancak, açık 'extern' anahtar kelimesi olsun veya olmasın, bir extern değişkeninin birden fazla tanımına dayanan bir program, kesinlikle uygun bir program değildir ve her yerde çalışacağı garanti edilmez. Eşdeğer olarak: kendini gösterebilen veya göstermeyen bir hata içerir.

Yönergeleri ihlal etmek

Elbette, bu kılavuz ilkelerin kırılabileceği birçok yol vardır. Bazen, yönergeleri ihlal etmek için iyi bir neden olabilir, ancak bu gibi durumlar son derece sıra dışıdır.

faulty_header.h

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

Not 1: başlık değişkeni externanahtar sözcük olmadan tanımlarsa, başlığı içeren her dosya değişkenin belirsiz bir tanımını oluşturur. Daha önce belirtildiği gibi, bu genellikle işe yarayacaktır, ancak C standardı çalışacağını garanti etmez.

broken_header.h

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

Not 2: üstbilgi değişkeni tanımlar ve başlatırsa, belirli bir programdaki yalnızca bir kaynak dosya üstbilgiyi kullanabilir. Başlıklar öncelikle bilgi paylaşımı için olduğundan, yalnızca bir kez kullanılabilen bir tane oluşturmak biraz saçmadır.

seldom_correct.h

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

Not 3: başlık statik bir değişken tanımlıyorsa (başlatma ile veya başlatma olmadan), her kaynak dosya kendi 'global' değişkeninin kendi özel sürümüyle sona erer.

Değişken aslında karmaşık bir diziyse, bu, kodun aşırı çoğaltılmasına yol açabilir. Çok nadiren, bir etki elde etmek için mantıklı bir yol olabilir, ancak bu çok sıra dışıdır.


özet

Önce gösterdiğim başlık tekniğini kullan. Güvenilir ve her yerde çalışır. Özellikle, bunu bildiren başlığın global_variable, onu tanımlayan dosya da dahil olmak üzere, onu kullanan her dosyada bulunduğunu unutmayın. Bu, her şeyin kendi kendine tutarlı olmasını sağlar.

Benzer kaygılar işlevleri beyan etme ve tanımlamada da ortaya çıkar - benzer kurallar geçerlidir. Ama soru özellikle değişkenler hakkındaydı, bu yüzden sadece değişkenlerin cevabını sakladım.

Orijinal Yanıtın Sonu

Deneyimli bir C programcısı değilseniz, muhtemelen burada okumayı bırakmalısınız.


Geç Büyük Ekleme

Kod Çoğaltmayı Önleme

Burada açıklanan 'üstbilgilerdeki bildirimler, kaynaktaki tanımlar' mekanizması hakkında bazen (ve meşru olarak) ortaya çıkan endişelerden biri, eşzamanlı olarak tutulması gereken iki dosyanın olmasıdır - üstbilgi ve kaynak. Bu genellikle, bir makronun, normalde değişkenleri bildiren çift görev sunabilmesi için bir makronun kullanılabileceği bir gözlemle takip edilir, ancak başlık dahil edilmeden önce belirli bir makro ayarlandığında, bunun yerine değişkenleri tanımlar.

Başka bir endişe, değişkenlerin bir dizi 'ana programın' her birinde tanımlanması gerektiğidir. Bu normalde sahte bir kaygıdır; değişkenleri tanımlamak ve üretilen nesne dosyasını programların her birine bağlamak için bir C kaynak dosyası girmeniz yeterlidir.

Tipik bir şema, şu şekilde gösterilen orijinal global değişkeni kullanarak şu şekilde çalışır 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++);
}

Sonraki iki dosya kaynağı tamamlar 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;
}
  • prog3kullanımları prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

Değişken başlatma

Bu şemada gösterildiği gibi problem, global değişkenin başlatılmasını sağlamamasıdır. C99 veya C11 ve makrolar için değişken bağımsız değişken listeleriyle, başlatmayı da destekleyecek bir makro tanımlayabilirsiniz. (C89 ile ve makrolarda değişken argüman listeleri desteği olmadığında, keyfi olarak uzun başlatıcıları işlemenin kolay bir yolu yoktur.)

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

Ters içeriği #ifve #elsebloklar, böcek tarafından tanımlanan sabitleme Denis 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;
}

Açıkçası, oddball yapısının kodu normalde yazdığınız şey değildir, ancak noktayı gösterir. İkinci çağırma ilk bağımsız INITIALIZERIS { 41ve (bu örnekte tekil) kalan bir argümandır 43 }. Makrolar için değişken argüman listeleri için C99 veya benzeri destek olmadan, virgül içermesi gereken başlatıcılar çok sorunludur.

Denis Kniazhev'e göre doğru başlık file3b.hdahil (yerine fileba.h)


Sonraki iki dosya kaynağı tamamlar 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;
}
  • prog4kullanımları prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

Başlık Muhafızları

Herhangi bir başlık yeniden yayılmaya karşı korunmalıdır, böylece tür tanımları (enum, yapı veya birleşim türleri veya genellikle typedefs) sorun yaratmaz. Standart teknik, başlık gövdesini aşağıdaki gibi bir başlık koruyucusuna sarmaktır:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Başlık dolaylı olarak iki kez dahil edilebilir. Örneğin, gösterilmeyen ve her iki üstbilgiyi de kullanmanız gereken bir tür tanımı file4b.hiçeriyorsa ve çözülmesi gereken daha zor sorunlarınız vardır. Açıkça, başlık listesini yalnızca içerecek şekilde revize edebilirsiniz . Ancak, iç bağımlılıkların farkında olmayabilirsiniz ve kod ideal olarak çalışmaya devam etmelidir.file3b.hfile1b.cfile4b.hfile3b.hfile4b.h

Dahası, zorlaşmaya başlar, çünkü tanımları oluşturmak için dahil file4b.hetmeden önce dahil file3b.hedebilirsiniz, ancak normal başlık korumaları file3b.hbaşlığın yeniden eklenmesini engelleyecektir.

Bu nedenle, file3b.hbildirimler için en fazla bir kez ve tanımlar için en fazla bir kez gövdeyi eklemeniz gerekir , ancak hem tek bir çeviri birimine (TU - bir kaynak dosyasının ve kullandığı başlıkların bir birleşimi) ihtiyacınız olabilir.

Değişken tanımlarıyla çoklu içerme

Ancak, makul olmayan bir kısıtlamaya tabi olarak yapılabilir. Yeni bir dosya adı seti tanıtalım:

  • external.h EXTERN makro tanımları vb. için

  • file1c.htürleri tanımlamak (özellikle, struct oddballtürü oddball_struct).

  • file2c.h global değişkenleri tanımlamak veya beyan etmek.

  • file3c.c küresel değişkenleri tanımlar.

  • file4c.c küresel değişkenleri kullanır.

  • file5c.c global değişkenleri tanımlayabileceğinizi ve tanımlayabileceğinizi gösterir.

  • file6c.c global değişkenleri tanımlayabileceğinizi ve sonra (denemeye çalıştığınızı) gösterir.

Bu örneklerde file5c.cve file6c.cbaşlığı file2c.hbirkaç kez doğrudan ekleyin , ancak mekanizmanın çalıştığını göstermenin en basit yolu budur. Bu, başlık dolaylı olarak iki kez dahil edilirse, güvenli olacağı anlamına gelir.

Bunun çalışması için kısıtlamalar:

  1. Genel değişkenleri tanımlayan veya bildiren üstbilginin kendisi herhangi bir tür tanımlamayabilir.

  2. Değişkenleri tanımlaması gereken bir üstbilgiyi eklemeden hemen önce DEFINE_VARIABLES makrosunu tanımlarsınız.

  3. Değişkenleri tanımlayan veya bildiren başlık stilize edilmiş içeriklere sahiptir.

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

Bir sonraki kaynak dosyası için kaynak (ana programı sağlar) tamamlar prog5, prog6ve 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;
}
  • prog5kullanımları prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog6kullanımları prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog7kullanımları prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.


Bu şema çoğu sorunu önler. Yalnızca değişkenleri tanımlayan file2c.hbir başlık (örneğin file7c.h), değişkenleri tanımlayan başka bir üstbilgiye (say ) dahil edilirse bir sorunla karşılaşırsınız . Bu konuda "yapma" dışında kolay bir yol yoktur.

Sen kısmen revize ederek sorunu çalışabilirsiniz file2c.hiçine 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 */

Sorun 'başlık içermeli #undef DEFINE_VARIABLESmi?' Bunu başlıktan çıkarırsanız ve herhangi bir tanımlayıcı çağrıyı #defineve ile sararsanız #undef:

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

kaynak kodunda (başlıklar hiçbir zaman değerini değiştirmez DEFINE_VARIABLES), o zaman temiz olmalısınız. Bu ekstra satır yazmak hatırlamak zorunda sadece bir sıkıntı. Bir alternatif şunlar olabilir:

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

Bu biraz dolambaçlı alma, ancak (kullanarak güvenli görünüyor file2d.hhiçbir ile #undef DEFINE_VARIABLESde 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; }

Sonraki iki dosya prog8ve için kaynağı tamamlar 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;
}
  • prog8kullanımları prog8.c, file7c.c, file9c.c.

  • prog9kullanımları prog8.c, file8c.c, file9c.c.


Bununla birlikte, uygulamalarda, özellikle de standart tavsiyeleri

Global değişkenlerden kaçının


Bu sergi bir şey kaçırıyor mu?

İtiraf : Burada özetlenen 'yinelenen koddan kaçınma' şeması geliştirildi, çünkü sorun üzerinde çalıştığım (ama sahip olmadığım) bazı kodları etkiliyor ve cevabın ilk bölümünde özetlenen şema ile boğucu bir endişe. Bununla birlikte, orijinal şema, değişken tanımlarını ve bildirimlerini senkronize tutmak için değiştirmek için sadece iki yer bırakır; bu, kod tabanı boyunca dağılmış değişken değişken bildirimlerinin (toplamda binlerce dosya olduğunda gerçekten önemli) büyük bir adımdır. . Bununla birlikte, fileNc.[ch](artı external.hve externdef.h) adlarına sahip dosyalardaki kod, çalışmanın yapılabileceğini gösterir. Açıkçası, üstbilgi dosyası tanımlayan ve bildiren bir değişken için standartlaştırılmış şablon vermek üzere bir başlık üreteci komut dosyası oluşturmak zor olmaz.

NB Bunlar, marjinal olarak ilginç hale getirmek için yeterli koda sahip oyuncak programlardır. Örnekler içinde kaldırılabilecek, ancak pedagojik açıklamayı basitleştirmek için tekrar yoktur. (Örnek: arasındaki fark prog5.cve prog8.ciçerdiği başlıkların birinin adı O nedenle kod yeniden düzenlemek için mümkün olacaktır. main()Yinelenir değil, ama daha çok ortaya daha gizlemek olacaktır.)


3
@litb: ortak tanım için Ek J.5.11'e bakınız - bu yaygın bir uzantıdır.
Jonathan Leffler

3
@litb: ve bundan kaçınılması gerektiğine katılıyorum - bu yüzden 'Küresel değişkenleri tanımlamak için çok iyi bir yol değil' bölümünde yer alıyor.
Jonathan Leffler

3
Aslında bu yaygın bir uzantıdır, ancak bir programın ona güvenmesi için tanımlanmamış bir davranıştır. Buna C'nin kendi kuralları tarafından izin verilip verilmediğini söyleyip söylemediğin belli değildi. Şimdi sadece ortak bir uzantı olduğunu söylüyorum ve taşınabilir olması için kod ihtiyacınız varsa önlemek için. Böylece sizi şüphesiz yükseltebilirim. Gerçekten harika cevap IMHO :)
Johannes Schaub - litb

19
Üstte durursanız, basit şeyleri basit tutar. Daha fazla okudukça, daha fazla nüans, komplikasyon ve ayrıntı ile ilgilenir. Daha az deneyimli C programcıları veya konuyu zaten bilen C programcıları için iki 'erken durma noktası' ekledim. Cevabı zaten biliyorsanız hepsini okumanıza gerek yoktur (ancak teknik bir hata bulursanız bana bildirin).
Jonathan Leffler

4
@supercat: Bana göre, dizi boyutu için bir numaralandırma değeri elde etmek için C99 dizi değişmezlerini kullanabilirsiniz, ( foo.h) ile örneklenir : #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }dizi için başlatıcıyı tanımlamak, dizinin enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };boyutunu almak ve diziyi extern int foo[];bildirmek için . Açıkçası, tanım sadece olmalıdır int foo[FOO_SIZE] = FOO_INITIALIZER;, ancak boyutun tanımlamaya gerçekten dahil edilmesi gerekmez. Bu size bir tamsayı sabiti getirir FOO_SIZE.
Jonathan Leffler

125

Bir externdeğişken bir çeviri birimi içinde tanımlanan bir değişkenin (düzeltilmesi için SBI) sayesinde bir bildirimidir. Bu, değişkenin depolamasının başka bir dosyaya tahsis edildiği anlamına gelir.

Diyelim ki iki .cdosyanız var test1.cve test2.c. Bir global değişken tanımlarsanız int test1_var;içinde test1.cve sen bu değişkeni erişmek istediğiniz test2.ckullanmak zorunda extern int test1_var;içinde test2.c.

Tam örnek:

$ 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
Hiçbir "sözde tanım" yoktur. Bu bir deklarasyon.
sbi

3
Yukarıdaki örnekte, değişirse extern int test1_var;için int test1_var;, bağlayıcı (gcc 5.4.0), yine de geçer. Peki, externbu durumda gerçekten gerekli mi?
radiohead

2
@radiohead: Benim içinde cevap , size bırakarak bu bilgileri bulacaksınız externgenellikle çalışan bir ortak uzantısıdır - ve özellikle GCC ile çalışır (ama GCC destekleri bunu sadece derleyici olmaktan uzaktır; bu yaygın Unix sistemleri üzerinde olduğu). (- Tanıdığım Cevabıma içinde "J.5.11" veya bölüm "Çok iyi değil yol" için bakabilirsiniz olduğu uzunluğunda) ve yakın metni (bunu yapmak için veya çalışır) bunu açıklar.
Jonathan Leffler

Harici bir beyanın kesinlikle başka bir çeviri biriminde tanımlanması gerekmez (ve genellikle değildir). Aslında, beyan ve tanım bir ve aynı olabilir.
Monica

40

Extern, değişkenin kendisinin başka bir çeviri biriminde olduğunu bildirmek için kullandığınız anahtar kelimedir.

Böylece bir çeviri biriminde bir değişken kullanmaya karar verebilir ve daha sonra başka bir değişkene erişebilirsiniz, sonra ikincisinde extern olarak ilan edersiniz ve sembol bağlayıcı tarafından çözülür.

Eğer extern olarak bildirmezseniz, aynı adlı ancak hiç ilişkili olmayan 2 değişken ve değişkenin çoklu tanımlarında bir hata alırsınız.


5
Başka bir deyişle, extern'in kullanıldığı çeviri birimi bu değişkeni, türünü vb. Bilir ve dolayısıyla temel mantıktaki kaynak kodunun onu kullanmasına izin verir, ancak değişkeni tahsis etmez , başka bir çeviri birimi bunu yapacaktır. Her iki çeviri birimi de değişkeni normal olarak bildirecek olsaydı, değişken için derlenmiş kod içinde ilişkili "yanlış" referanslar ve bağlayıcı için sonuçta ortaya çıkan belirsizlikle birlikte iki fiziksel konum olurdu.
mjv

26

Bir extern değişkenini derleyiciye verdiğiniz bir söz olarak düşünmeyi seviyorum.

Bir extern ile karşılaştığında, derleyici sadece türünü bulabilir, nerede yaşadığını değil, referansı çözemez.

"Güven bana. Bağlantı zamanında bu referans çözülebilir." Diyorsunuz.


Daha genel olarak, bir bildirim , adın bağlantı zamanında tam olarak bir tanıma çözümlenebileceğine dair bir vaattir. Bir extern bir değişkeni tanımlamaksızın bildirir.
Yalan Ryan

18

extern derleyiciye bu değişkenin belleğinin başka bir yerde bildirildiğine güvenmesini söyler, bu nedenle belleği ayırmaya / kontrol etmeye çalışmaz.

Bu nedenle, bir extern referansı olan bir dosyayı derleyebilirsiniz, ancak bu bellek bir yerde bildirilmezse bağlantı kuramazsınız.

Global değişkenler ve kütüphaneler için kullanışlıdır, ancak linker check yazmadığı için tehlikelidir.


Bellek bildirilmedi. Daha fazla ayrıntı için bu sorunun yanıtlarına bakın: stackoverflow.com/questions/1410563 .
sbi

15

Bir ekleme externdöner değişken tanımı değişken olarak beyan . Bir bildirim ve tanım arasındaki farkın ne olduğu hakkında bu konuya bakın .


int fooVe extern int foo(dosya kapsamı) arasındaki fark nedir ? İkisi de deklarasyon, değil mi?

@ user14284: Her ikisi de sadece her tanımın da bir deklarasyon olması anlamında beyan. Ama bunun bir açıklamasıyla bağlantı kurdum. ("Bir bildirim ve tanım arasındaki farkın ne olduğuna dair bu konuya bakın.") Neden bağlantıyı basitçe takip edip okumuyorsunuz?
sbi

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

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

Bildirim bellek ayırmayacak (değişken bellek ayırma için tanımlanmalıdır), ancak tanım olacaktır. Bu, extern anahtar kelimesinin başka bir basit görünümüdür, çünkü diğer cevaplar gerçekten harika.


11

Extern'in doğru yorumu derleyiciye bir şey anlatmanızdır. Derleyiciye, şu anda mevcut olmamasına rağmen, bildirilen değişkenin bir şekilde bağlayıcı (genellikle başka bir nesnede (dosyada)) bulunacağını söylersiniz. Bağlayıcı daha sonra, herhangi bir extern beyanı olsun ya da olmasın, her şeyi bulmak ve bir araya getirmek için şanslı adam olacaktır.


8

C'de bir dosyanın içindeki bir değişken, example.c'ye yerel kapsam verildiğini söylüyor. Derleyici, değişkenin tanımını aynı dosya example.c içinde olmasını bekler ve bunu bulamadığında bir hata verir. Öte yandan bir işlev varsayılan olarak genel kapsama sahiptir. Böylece derleyici "görünüm dostum ... bu işlevin tanımını burada bulabilirsiniz" açık söz etmek zorunda değilsiniz. Bildirimini içeren dosyayı içeren bir işlev için yeterlidir (Üstbilgi dosyasını gerçekten çağırdığınız dosya). Örneğin, şu 2 dosyayı göz önünde bulundurun:
example.c

#include<stdio.h>
extern int a;
main(){
       printf("The value of a is <%d>\n",a);
}

example1.c

int a = 5;

Şimdi aşağıdaki komutları kullanarak iki dosyayı birlikte derlediğinizde:

adım 1) cc -o ex örnek.c örnek1.c adım 2) ./ ex

Aşağıdaki çıktıyı alırsınız: a'nın değeri <5>


8

GCC ELF Linux uygulaması

Diğer cevaplar dil kullanım yönünü ele almıştır, bu yüzden şimdi bu uygulamada nasıl uygulandığına bakalım.

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

Derleme ve derleme:

gcc -c main.c
readelf -s main.o

Çıktı şunları içerir:

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

Sistem V ABI Güncelleme ELF Spec "Sembol Tablosu" bölüm açıklıyor:

SHN_UNDEF Bu bölüm tablosu dizini, sembolün tanımsız olduğu anlamına gelir. Bağlantı düzenleyicisi bu nesne dosyasını belirtilen sembolü tanımlayan başka bir dosyayla birleştirdiğinde, bu dosyanın sembole referansları gerçek tanıma bağlanır.

temelde C standardının verdiği davranış extern değişkenlere .

Şu andan itibaren, son programı yapmak bağlayıcının görevidir, ancak externbilgi kaynak kodundan nesne dosyasına zaten çıkarılmıştır.

GCC 4.8'de test edilmiştir.

C ++ 17 satır içi değişkenler

C ++ 17'de, kullanımı basit (başlıkta yalnızca bir kez tanımlanabilir) ve daha güçlü (destek constexpr) olduğu için, extern değişkenleri yerine satır içi değişkenler kullanmak isteyebilirsiniz. Bakınız: C ve C ++ 'da' const static 'ne anlama geliyor?


3
Bu benim aşağı oyum değil, bu yüzden bilmiyorum. Ancak bir görüş bildireceğim. Her ne kadar çıktısına bakmak readelfya nmda yardımcı olabilmekle externbirlikte, ilk programı nasıl kullanacağınızla ilgili temel bilgileri açıklamamış ya da gerçek tanımı ile ilk programı tamamlamamışsınızdır. Kodunuz bile kullanılmıyor notExtern. Bir isimlendirme problemi de var: notExternburada tanımlanmak yerine tanımlanmış olsa da , externbu çeviri birimleri uygun bir bildirim içeriyorsa (ihtiyaç duyacaktır extern int notExtern;!) Diğer kaynak dosyalar tarafından erişilebilen harici bir değişkendir .
Jonathan Leffler

1
@JonathanLeffler geribildirim için teşekkürler! Standart davranış ve kullanım önerileri diğer yanıtlarda zaten yapılmış, bu yüzden neler olduğunu kavramama gerçekten yardımcı olan uygulamayı biraz göstermeye karar verdim. Kullanmamak notExternçirkin, düzeltildi. İsimlendirme hakkında, daha iyi bir ismin olup olmadığını bana bildirin. Tabii ki bu gerçek bir program için iyi bir isim değil, ama bence burada didaktik role iyi uyuyor.
Ciro Santilli 法轮功 at 审查 六四 事件 法轮功

Adlara gelince, global_defburada tanımlanan değişken ve extern_refbaşka bir modülde tanımlanan değişken için ne dersiniz ? Açık bir simetriye sahip olurlar mıydı? Hala int extern_ref = 57;tanımlandığı dosyada veya bunun gibi bir şeyle sonuçlanırsınız , bu nedenle ad oldukça ideal değildir, ancak tek kaynak dosya bağlamında makul bir seçimdir. Having extern int global_def;bir başlıkta kadar bir sorun nedeniyle, bana öyle geliyor değil. Tamamen size kalmış, elbette.
Jonathan Leffler

7

extern anahtar sözcüğü, değişkenle birlikte genel değişken olarak tanımlanması için kullanılır.

Ayrıca, extern anahtar sözcüğü kullanılarak bildirilen değişkeni, başka bir dosyada bildirilmiş / tanımlanmış olsa da, herhangi bir dosyada kullanabileceğinizi gösterir.


5

extern programınızın bir modülünün, programınızın başka bir modülünde bildirilen global bir değişkene veya işleve erişmesine olanak tanır. Genellikle başlık dosyalarında bildirilen extern değişkenleriniz vardır.

Bir programın değişkenlerinize veya işlevlerinize erişmesini istemiyorsanız static, derleyiciye bu değişkenin veya işlevin bu modülün dışında kullanılamayacağını bildiren bir komut kullanırsınız.


5

extern basitçe bir değişkenin başka bir yerde tanımlandığı anlamına gelir (örneğin, başka bir dosyada).


4

İlk olarak, externanahtar kelime bir değişkeni tanımlamak için kullanılmaz; daha ziyade bir değişken bildirmek için kullanılır. externBir veri sınıfı değil, bir depolama sınıfı olduğunu söyleyebilirim .

externdiğer C dosyalarının veya harici bileşenlerin bu değişkenin zaten bir yerde tanımlandığını bilmesini sağlamak için kullanılır. Örnek: Bir kütüphane oluşturuyorsanız, global değişkeni kütüphanenin kendisinde bir yerde zorunlu olarak tanımlamanıza gerek yoktur. Kütüphane doğrudan derlenecektir, ancak dosyayı bağlarken tanımı kontrol eder.


3

extern, bir first.cdosyanın başka bir second.cdosyadaki genel parametreye tam erişime sahip olması için kullanılır .

externBildirilebilir first.cdosya veya başlık dosyalarının hiçbirinde first.ckapsamaktadır.


3
Tür değiştiğinde, bildirim de değişecek şekilde, bildirimin externiçinde değil, başlıkta olması gerektiğini unutmayın first.c. Ayrıca, second.ctanımın bildirimle tutarlı olmasını sağlamak için değişkeni bildiren başlık eklenmelidir . Başlıktaki açıklama, hepsini bir arada tutan yapıştırıcıdır; dosyaların ayrı olarak derlenmesine izin verir, ancak genel değişkenin türünü tutarlı bir şekilde görmelerini sağlar.
Jonathan Leffler

2

Xc8 ile, her dosyada bir değişkeni yanlış bir intşekilde bir dosya ve bir dosyadachar başka söyleyebildiğiniz . Bu, değişkenlerin bozulmasına yol açabilir.

Bu sorun yaklaşık 15 yıl önce bir mikroçip forumunda zarif bir şekilde çözüldü / * Bkz. "Http: www.htsoft.com" / / "forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / page / 0 # 18766"

Ancak bu bağlantının artık işe yaramadığı görülüyor ...

Bu yüzden çabucak açıklamaya çalışacağım; global.h adlı bir dosya oluşturun.

İçinde aşağıdakileri beyan eder

#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

Şimdi main.c dosyasında

#define MAIN_C 1
#include "global.h"
#undef MAIN_C

Bu, main.c'de değişkenin bir unsigned char .

Şimdi global.h dahil olmak üzere diğer dosyalarda bu dosya için bir extern olarak ilan edilecektir .

extern unsigned char testing_mode;

Ancak doğru bir şekilde ilan edilecektir. unsigned char .

Eski forum gönderisi bunu biraz daha açık bir şekilde açıkladı. Ancak bu, bir gotchadosyadaki bir değişkeni bildirmenize ve daha sonra başka bir dosyada başka bir tür olarak extern olarak bildirmenize izin veren bir derleyici kullanırken gerçek bir potansiyeldir . Bununla ilgili sorunlar, test_mode'u başka bir dosyada int olarak ilan ederseniz, bunun 16 bitlik bir var olduğunu düşünür ve koçun başka bir bölümünün üzerine yazar ve muhtemelen başka bir değişkeni bozar. Hata ayıklamak zor!


0

Bir başlık dosyasının extern referansını veya bir nesnenin gerçek uygulamasını içermesine izin vermek için kullandığım çok kısa bir çözüm. Aslında nesneyi içeren dosyada #define GLOBAL_FOO_IMPLEMENTATION. Daha sonra bu dosyaya yeni bir nesne eklediğimde, tanımı kopyalayıp yapıştırmak zorunda kalmadan bu dosyada da görünür.

Bu kalıbı birden fazla dosyada kullanıyorum. İşleri mümkün olduğunca kendi içinde tutmak için, her başlıktaki tek GLOBAL makroyu tekrar kullanıyorum. Başlığım şöyle görünüyor:

//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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.