Bagaimana cara menggunakan extern untuk berbagi variabel antara file sumber?


988

Saya tahu bahwa variabel global dalam C terkadang memiliki externkata kunci. Apa itu externvariabel? Seperti apa deklarasi itu? Apa cakupannya?

Ini terkait dengan berbagi variabel di seluruh file sumber, tetapi bagaimana cara kerjanya secara tepat? Di mana saya menggunakan extern?

Jawaban:


1751

Penggunaan externhanya relevan ketika program yang Anda buat terdiri dari beberapa file sumber yang dihubungkan bersama, di mana beberapa variabel didefinisikan, misalnya, dalam file sumber file1.cperlu direferensikan dalam file sumber lain, seperti file2.c.

Penting untuk memahami perbedaan antara mendefinisikan variabel dan mendeklarasikan variabel :

  • Variabel dideklarasikan ketika kompiler diberitahu bahwa ada variabel (dan ini adalah tipenya); itu tidak mengalokasikan penyimpanan untuk variabel pada saat itu.

  • Variabel didefinisikan ketika kompiler mengalokasikan penyimpanan untuk variabel.

Anda dapat mendeklarasikan variabel beberapa kali (meskipun cukup satu kali); Anda hanya dapat mendefinisikannya sekali dalam lingkup yang diberikan. Definisi variabel juga merupakan deklarasi, tetapi tidak semua deklarasi variabel adalah definisi.

Cara terbaik untuk mendeklarasikan dan mendefinisikan variabel global

Cara bersih, andal untuk mendeklarasikan dan mendefinisikan variabel global adalah dengan menggunakan file header untuk memuat extern deklarasi variabel.

Header disertakan oleh satu file sumber yang mendefinisikan variabel dan oleh semua file sumber yang merujuk variabel. Untuk setiap program, satu file sumber (dan hanya satu file sumber) mendefinisikan variabel. Demikian pula, satu file header (dan hanya satu file header) harus mendeklarasikan variabel. File header sangat penting; ini memungkinkan pemeriksaan silang antara TU independen (unit terjemahan - file sumber think) dan memastikan konsistensi.

Meskipun ada cara lain untuk melakukannya, metode ini sederhana dan dapat diandalkan. Hal ini ditunjukkan oleh file3.h, file1.cdan 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++);
}

Itu cara terbaik untuk mendeklarasikan dan mendefinisikan variabel global.


Dua file selanjutnya melengkapi sumber untuk prog1:

Program lengkap yang ditampilkan menggunakan fungsi, sehingga deklarasi fungsi telah merayap masuk. Baik C99 dan C11 membutuhkan fungsi untuk dideklarasikan atau didefinisikan sebelum digunakan (sedangkan C90 tidak, untuk alasan yang baik). Saya menggunakan kata kunci externdi depan deklarasi fungsi di header untuk konsistensi - untuk mencocokkan externdi depan deklarasi variabel dalam header. Banyak orang memilih untuk tidak menggunakan externdeklarasi fungsi di depan; kompiler tidak peduli - dan pada akhirnya, saya juga tidak selama Anda konsisten, setidaknya dalam file sumber.

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;
}
  • prog1kegunaan prog1.c, file1.c, file2.c, file3.hdan prog1.h.

File prog1.mkini prog1hanya untuk makefile . Ini akan bekerja dengan sebagian besar versi yang makediproduksi sejak pergantian milenium. Itu tidak terikat khusus untuk GNU Make.

prog1.mk

# Minimal makefile for prog1

PROGRAM = prog1
FILES.c = prog1.c file1.c file2.c
FILES.h = prog1.h file3.h
FILES.o = ${FILES.c:.c=.o}

CC      = gcc
SFLAGS  = -std=c11
GFLAGS  = -g
OFLAGS  = -O3
WFLAG1  = -Wall
WFLAG2  = -Wextra
WFLAG3  = -Werror
WFLAG4  = -Wstrict-prototypes
WFLAG5  = -Wmissing-prototypes
WFLAGS  = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5}
UFLAGS  = # Set on command line only

CFLAGS  = ${SFLAGS} ${GFLAGS} ${OFLAGS} ${WFLAGS} ${UFLAGS}
LDFLAGS =
LDLIBS  =

all:    ${PROGRAM}

${PROGRAM}: ${FILES.o}
    ${CC} -o $@ ${CFLAGS} ${FILES.o} ${LDFLAGS} ${LDLIBS}

prog1.o: ${FILES.h}
file1.o: ${FILES.h}
file2.o: ${FILES.h}

# If it exists, prog1.dSYM is a directory on macOS DEBRIS = a.out core *~ *.dSYM RM_FR = rm -fr 

clean:
    ${RM_FR} ${FILES.o} ${PROGRAM} ${DEBRIS}

Pedoman

Aturan untuk dilanggar oleh para ahli saja, dan hanya dengan alasan yang bagus:

  • File header hanya berisi externdeklarasi variabel - tidak pernah staticatau definisi variabel tidak memenuhi syarat.

  • Untuk variabel apa pun, hanya satu file header yang menyatakannya (SPOT - Single Point of Truth).

  • File sumber tidak pernah berisi externdeklarasi variabel - file sumber selalu menyertakan header (tunggal) yang menyatakannya.

  • Untuk setiap variabel yang diberikan, tepat satu file sumber mendefinisikan variabel, lebih baik menginisialisasi juga. (Meskipun tidak perlu menginisialisasi secara eksplisit ke nol, tidak ada salahnya dan dapat melakukan beberapa kebaikan, karena hanya ada satu definisi yang diinisialisasi dari variabel global tertentu dalam suatu program).

  • File sumber yang mendefinisikan variabel juga termasuk header untuk memastikan bahwa definisi dan deklarasi konsisten.

  • Suatu fungsi seharusnya tidak perlu mendeklarasikan variabel menggunakan extern.

  • Hindari variabel global jika memungkinkan - gunakan fungsi.

Kode sumber dan teks jawaban ini tersedia di repositori SOQ (Stack Overflow Pertanyaan) saya di GitHub di sub-direktori src / so-0143-3204 .

Jika Anda bukan seorang programmer C yang berpengalaman, Anda bisa (dan mungkin harus) berhenti membaca di sini.

Cara yang tidak begitu baik untuk mendefinisikan variabel global

Dengan beberapa kompiler C (memang, banyak), Anda bisa lolos dengan apa yang disebut definisi variabel 'umum' juga. 'Biasa', di sini, mengacu pada teknik yang digunakan dalam Fortran untuk berbagi variabel antara file sumber, menggunakan (mungkin dinamai) blok UMUM. Apa yang terjadi di sini adalah bahwa masing-masing dari sejumlah file memberikan definisi tentatif dari variabel. Selama tidak lebih dari satu file memberikan definisi yang diinisialisasi, maka berbagai file akhirnya berbagi definisi tunggal yang sama dari variabel:

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

Teknik ini tidak sesuai dengan huruf standar C dan 'aturan satu definisi' - itu adalah perilaku yang tidak terdefinisi secara resmi:

J.2 Perilaku tidak terdefinisi

Identifier dengan tautan eksternal digunakan, tetapi dalam program tidak ada persis satu definisi eksternal untuk identifier, atau identifier tidak digunakan dan ada beberapa definisi eksternal untuk identifier (6.9).

§6.9 Definisi eksternal ¶5

Sebuah definisi eksternal adalah deklarasi eksternal yang juga merupakan definisi dari sebuah fungsi (selain definisi inline) atau benda. Jika pengidentifikasi yang dinyatakan dengan tautan eksternal digunakan dalam ekspresi (selain sebagai bagian dari operan operator sizeofatau _Alignofyang hasilnya adalah konstanta bilangan bulat), di suatu tempat di seluruh program harus ada tepat satu definisi eksternal untuk pengidentifikasi; jika tidak, tidak akan ada lebih dari satu. 161)

161) Jadi, jika pengidentifikasi yang dinyatakan dengan tautan eksternal tidak digunakan dalam ekspresi, tidak perlu ada definisi eksternal untuk itu.

Namun, standar C juga mencantumkannya dalam Lampiran J informatif sebagai salah satu ekstensi Umum .

J.5.11 Beberapa definisi eksternal

Mungkin ada lebih dari satu definisi eksternal untuk pengidentifikasi objek, dengan atau tanpa penggunaan kata kunci eksternal; jika definisi tidak setuju, atau lebih dari satu diinisialisasi, perilaku tidak terdefinisi (6.9.2).

Karena teknik ini tidak selalu didukung, yang terbaik adalah menghindari menggunakannya, terutama jika kode Anda harus portabel . Dengan menggunakan teknik ini, Anda juga bisa berakhir dengan hukuman jenis yang tidak disengaja.

Jika salah satu file di atas dideklarasikan lsebagai doublebukan sebagai a long, tipe linker tidak aman C mungkin tidak akan menemukan ketidakcocokan. Jika Anda menggunakan mesin dengan 64-bit longdan double, Anda bahkan tidak akan mendapatkan peringatan; pada mesin dengan 32-bit longdan 64-bit double, Anda mungkin akan mendapatkan peringatan tentang ukuran yang berbeda - linker akan menggunakan ukuran terbesar, persis seperti program Fortran mengambil ukuran terbesar dari setiap blok umum.

Perhatikan bahwa GCC 10.1.0, yang dirilis pada 2020-05-07, mengubah opsi kompilasi default untuk digunakan -fno-common, yang berarti bahwa secara default, kode di atas tidak lagi terhubung kecuali Anda menimpa default dengan -fcommon(atau menggunakan atribut, dll - lihat tautan).


Dua file selanjutnya melengkapi sumber untuk 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();
}
  • prog2kegunaan prog2.c, file10.c, file11.c, file12.c, prog2.h.

Peringatan

Seperti dicatat dalam komentar di sini, dan sebagaimana dinyatakan dalam jawaban saya untuk pertanyaan yang sama , menggunakan beberapa definisi untuk variabel global mengarah ke perilaku yang tidak terdefinisi (J.2; §6.9), yang merupakan cara standar untuk mengatakan "apa pun bisa terjadi". Salah satu hal yang dapat terjadi adalah bahwa program berperilaku seperti yang Anda harapkan; dan J.5.11 mengatakan, kira-kira, "Anda mungkin lebih beruntung daripada yang pantas Anda dapatkan". Tetapi sebuah program yang bergantung pada definisi berganda dari variabel eksternal - dengan atau tanpa kata kunci 'eksternal' eksplisit - bukan program yang sepenuhnya sesuai dan tidak dijamin untuk bekerja di mana saja. Setara: itu mengandung bug yang mungkin atau mungkin tidak muncul dengan sendirinya.

Melanggar pedoman

Tentu saja ada banyak cara di mana pedoman ini dapat dilanggar. Kadang-kadang, mungkin ada alasan bagus untuk melanggar pedoman, tetapi kesempatan seperti itu sangat tidak biasa.

faulty_header.h

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

Catatan 1: jika header mendefinisikan variabel tanpa externkata kunci, maka setiap file yang menyertakan header membuat definisi tentatif dari variabel. Seperti disebutkan sebelumnya, ini akan sering berhasil, tetapi standar C tidak menjamin bahwa itu akan berhasil.

broken_header.h

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

Catatan 2: jika header mendefinisikan dan menginisialisasi variabel, maka hanya satu file sumber dalam program yang diberikan dapat menggunakan header. Karena tajuk terutama untuk berbagi informasi, agak konyol untuk membuatnya yang hanya dapat digunakan satu kali.

seldom_correct.h

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

Catatan 3: jika header mendefinisikan variabel statis (dengan atau tanpa inisialisasi), maka setiap file sumber berakhir dengan versi pribadi dari variabel 'global'.

Jika variabel sebenarnya adalah array yang kompleks, misalnya, ini dapat menyebabkan duplikasi kode yang ekstrem. Ini bisa, sangat kadang-kadang, menjadi cara yang masuk akal untuk mencapai beberapa efek, tetapi itu sangat tidak biasa.


Ringkasan

Gunakan teknik tajuk yang saya tunjukkan pertama kali. Ia bekerja dengan andal dan di mana-mana. Perhatikan, khususnya, bahwa header yang menyatakan global_variabledisertakan dalam setiap file yang menggunakannya - termasuk yang mendefinisikannya. Ini memastikan bahwa semuanya konsisten sendiri.

Kekhawatiran serupa muncul dengan mendeklarasikan dan mendefinisikan fungsi - aturan analog berlaku. Tetapi pertanyaannya adalah tentang variabel khusus, jadi saya hanya menyimpan jawaban untuk variabel.

Akhir dari Jawaban Asli

Jika Anda bukan seorang programmer C yang berpengalaman, Anda mungkin harus berhenti membaca di sini.


Penambahan Utama Terlambat

Menghindari Duplikasi Kode

Satu keprihatinan yang kadang-kadang (dan secara sah) diajukan tentang mekanisme 'deklarasi dalam tajuk, definisi dalam sumber' yang diuraikan di sini adalah bahwa ada dua file yang harus disinkronkan - tajuk dan sumber. Ini biasanya ditindaklanjuti dengan pengamatan bahwa makro dapat digunakan sehingga header melayani tugas ganda - biasanya mendeklarasikan variabel, tetapi ketika makro tertentu diatur sebelum header dimasukkan, itu mendefinisikan variabel sebagai gantinya.

Kekhawatiran lain dapat berupa bahwa variabel perlu didefinisikan di masing-masing sejumlah 'program utama'. Ini biasanya kekhawatiran palsu; Anda cukup memperkenalkan file sumber C untuk mendefinisikan variabel dan menautkan file objek yang dihasilkan dengan masing-masing program.

Skema tipikal bekerja seperti ini, menggunakan variabel global asli yang diilustrasikan dalam 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++);
}

Dua file selanjutnya melengkapi sumber untuk 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;
}
  • prog3kegunaan prog3.c, file1a.c, file2a.c, file3a.h, prog3.h.

Inisialisasi variabel

Masalah dengan skema ini seperti yang ditunjukkan adalah bahwa ia tidak menyediakan inisialisasi variabel global. Dengan C99 atau C11 dan daftar argumen variabel untuk makro, Anda bisa mendefinisikan makro untuk mendukung inisialisasi juga. (Dengan C89 dan tidak ada dukungan untuk daftar argumen variabel dalam makro, tidak ada cara mudah untuk menangani inisialisasi panjang yang sewenang-wenang.)

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

Membalikkan konten #ifdan #elseblokir, memperbaiki bug yang diidentifikasi oleh 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;
}

Jelas, kode untuk struktur eksentrik bukan apa yang biasanya Anda tulis, tetapi menggambarkan intinya. Argumen pertama untuk doa kedua INITIALIZERadalah { 41dan argumen yang tersisa (tunggal dalam contoh ini) adalah 43 }. Tanpa C99 atau dukungan serupa untuk daftar argumen variabel untuk makro, inisialisasi yang perlu mengandung koma sangat bermasalah.

Header yang benar file3b.hdisertakan (bukan fileba.h) per Denis Kniazhev


Dua file selanjutnya melengkapi sumber untuk 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;
}
  • prog4kegunaan prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

Pelindung Kepala

Header apa pun harus dilindungi terhadap reinklusi, sehingga definisi tipe (enum, tipe struct atau union, atau typedefs secara umum) tidak menyebabkan masalah. Teknik standar adalah membungkus tubuh tajuk dengan pelindung tajuk seperti:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Header mungkin dimasukkan dua kali secara tidak langsung. Misalnya, jika file4b.hmenyertakan file3b.hdefinisi jenis yang tidak ditampilkan, dan file1b.cperlu menggunakan tajuk file4b.hdan file3b.h, maka Anda memiliki beberapa masalah yang lebih rumit untuk diselesaikan. Jelas, Anda dapat merevisi daftar tajuk untuk memasukkan hanya file4b.h. Namun, Anda mungkin tidak mengetahui dependensi internal - dan kode tersebut harus, idealnya, terus bekerja.

Selanjutnya, itu mulai menjadi rumit karena Anda mungkin menyertakan file4b.hsebelum memasukkan file3b.huntuk menghasilkan definisi, tetapi penjaga header normal pada file3b.hakan mencegah header dimasukkan kembali.

Jadi, Anda perlu memasukkan badan file3b.hpaling banyak sekali untuk deklarasi, dan paling banyak sekali untuk definisi, tetapi Anda mungkin perlu keduanya dalam satu unit terjemahan (TU - kombinasi dari file sumber dan header yang digunakannya).

Inklusi berganda dengan definisi variabel

Namun, hal itu dapat dilakukan dengan batasan yang tidak terlalu tidak masuk akal. Mari kita perkenalkan set nama file baru:

  • external.h untuk definisi makro EKSTERNAL, dll.

  • file1c.huntuk menentukan jenis (terutama, struct oddballjenis oddball_struct).

  • file2c.h untuk mendefinisikan atau mendeklarasikan variabel global.

  • file3c.c yang mendefinisikan variabel global.

  • file4c.c yang hanya menggunakan variabel global.

  • file5c.c yang menunjukkan bahwa Anda dapat mendeklarasikan dan kemudian mendefinisikan variabel global.

  • file6c.c yang menunjukkan bahwa Anda dapat mendefinisikan dan kemudian (berusaha untuk) mendeklarasikan variabel global.

Dalam contoh-contoh ini, file5c.cdan file6c.csecara langsung sertakan header file2c.hbeberapa kali, tetapi itu adalah cara paling sederhana untuk menunjukkan bahwa mekanismenya bekerja. Ini berarti bahwa jika tajuk secara tidak langsung dimasukkan dua kali, itu juga akan aman.

Batasan agar ini berfungsi adalah:

  1. Header yang mendefinisikan atau mendeklarasikan variabel global mungkin tidak dengan sendirinya mendefinisikan tipe apa pun.

  2. Segera sebelum Anda menyertakan header yang harus mendefinisikan variabel, Anda mendefinisikan DEFINE_VARIABLES makro.

  3. Header yang mendefinisikan atau mendeklarasikan variabel memiliki konten yang distilisasi.

eksternal


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

File sumber selanjutnya melengkapi sumber (menyediakan program utama) untuk prog5, prog6dan 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;
}
  • prog5kegunaan prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog6kegunaan prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.

  • prog7kegunaan prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.


Skema ini menghindari sebagian besar masalah. Anda hanya mengalami masalah jika header yang mendefinisikan variabel (seperti file2c.h) dimasukkan oleh header lain (katakanlah file7c.h) yang mendefinisikan variabel. Tidak ada cara mudah untuk melakukannya selain "jangan lakukan itu".

Anda sebagian dapat bekerja di sekitar masalah dengan merevisi file2c.hke 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 */

Masalahnya menjadi 'haruskah header menyertakan #undef DEFINE_VARIABLES?' Jika Anda menghilangkannya dari tajuk dan membungkus doa apa saja dengan #definedan #undef:

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

dalam kode sumber (jadi header tidak pernah mengubah nilai DEFINE_VARIABLES), maka Anda harus bersih. Ini hanya gangguan jika harus ingat untuk menulis baris tambahan. Alternatifnya mungkin:

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

Ini sedikit berbelit-belit, tetapi tampaknya aman (menggunakan file2d.h, tanpa #undef DEFINE_VARIABLESdi 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; }

Dua file selanjutnya melengkapi sumber untuk prog8dan 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;
}
  • prog8kegunaan prog8.c, file7c.c, file9c.c.

  • prog9kegunaan prog8.c, file8c.c, file9c.c.


Namun, masalahnya relatif tidak mungkin terjadi dalam praktik, terutama jika Anda mengikuti saran standar

Hindari variabel global


Apakah paparan ini melewatkan sesuatu?

Pengakuan : Skema 'menghindari kode duplikat' yang diuraikan di sini dikembangkan karena masalah ini memengaruhi beberapa kode yang saya kerjakan (tetapi tidak dimiliki), dan merupakan kekhawatiran yang mengganggu dengan skema yang diuraikan di bagian pertama dari jawaban. Namun, skema asli memberi Anda hanya dua tempat untuk memodifikasi agar definisi dan deklarasi variabel tetap tersinkronisasi, yang merupakan langkah besar ke depan untuk memiliki deklarasi variabel eksternal yang tersebar di seluruh basis kode (yang benar-benar berarti ketika ada total ribuan file) . Namun, kode dalam file dengan nama fileNc.[ch](plus external.hdan externdef.h) menunjukkan bahwa itu dapat dibuat berfungsi. Jelas, tidak akan sulit untuk membuat script generator header untuk memberi Anda template standar untuk variabel yang mendefinisikan dan mendeklarasikan file header.

NB Ini adalah program mainan dengan kode yang nyaris tidak cukup untuk membuatnya sedikit menarik. Ada pengulangan dalam contoh-contoh yang dapat dihapus, tetapi tidak untuk menyederhanakan penjelasan pedagogis. (Misalnya: perbedaan antara prog5.cdan prog8.cadalah nama salah satu tajuk yang disertakan. Dimungkinkan untuk mengatur ulang kode sehingga main()fungsinya tidak diulangi, tetapi akan menyembunyikan lebih banyak daripada yang diungkapkannya.)


3
@ litb: lihat Lampiran J.5.11 untuk definisi umum - ini adalah ekstensi umum.
Jonathan Leffler

3
@ litb: dan saya setuju itu harus dihindari - itu sebabnya ada di bagian 'Cara yang tidak begitu baik untuk mendefinisikan variabel global'.
Jonathan Leffler

3
Memang itu adalah ekstensi umum, tapi itu perilaku yang tidak ditentukan untuk bergantung pada suatu program. Saya hanya tidak jelas apakah Anda mengatakan bahwa ini diizinkan oleh aturan C sendiri. Sekarang saya melihat Anda mengatakan itu hanya ekstensi umum dan untuk menghindarinya jika Anda membutuhkan kode Anda untuk menjadi portabel. Jadi saya dapat memperbaiki Anda tanpa keraguan. Benar-benar hebat jawaban IMHO :)
Johannes Schaub - litb

19
Jika Anda berhenti di atas, itu membuat hal-hal sederhana tetap sederhana. Ketika Anda membaca lebih jauh ke bawah, ini membahas lebih banyak nuansa, komplikasi, dan detail. Saya baru saja menambahkan dua 'titik henti awal' untuk programmer C yang kurang berpengalaman - atau programmer C yang sudah mengetahui subjeknya. Tidak perlu membaca semuanya jika Anda sudah tahu jawabannya (tapi beri tahu saya jika Anda menemukan kesalahan teknis).
Jonathan Leffler

4
@supercat: Ini terjadi kepada saya bahwa Anda dapat menggunakan liter array C99 untuk mendapatkan nilai enumerasi untuk ukuran array, dicontohkan oleh ( foo.h): #define FOO_INITIALIZER { 1, 2, 3, 4, 5 }untuk menentukan initializer untuk array, enum { FOO_SIZE = sizeof((int [])FOO_INITIALIZER) / sizeof(((int [])FOO_INITIALIZER)[0]) };untuk mendapatkan ukuran array, dan extern int foo[];untuk mendeklarasikan array . Jelas, definisi harus adil int foo[FOO_SIZE] = FOO_INITIALIZER;, meskipun ukurannya tidak benar-benar harus dimasukkan dalam definisi. Ini memberi Anda konstanta integer FOO_SIZE,.
Jonathan Leffler

125

Sebuah externvariabel adalah deklarasi (terima kasih kepada SBI untuk koreksi) dari variabel yang didefinisikan di unit lain terjemahan. Itu berarti penyimpanan untuk variabel dialokasikan di file lain.

Katakanlah Anda memiliki dua .cfile test1.cdan test2.c. Jika anda mendefinisikan variabel global int test1_var;di test1.cdan Anda ingin mengakses variabel ini dalam test2.cAnda harus menggunakan extern int test1_var;di test2.c.

Sampel lengkap:

$ 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
Tidak ada "definisi semu". Itu deklarasi.
sbi

3
Pada contoh di atas, jika saya mengubah extern int test1_var;to int test1_var;, linker (gcc 5.4.0) masih lewat. Jadi, apakah externbenar-benar dibutuhkan dalam kasus ini?
radiohead

2
@radiohead: Dalam jawaban saya , Anda akan menemukan informasi bahwa menjatuhkan externadalah ekstensi umum yang sering bekerja - dan secara khusus bekerja dengan GCC (tetapi GCC masih jauh dari satu-satunya kompiler yang mendukungnya; ini lazim pada sistem Unix). Anda dapat mencari "J.5.11" atau bagian "cara tidak begitu baik" dalam jawaban saya (saya tahu - itu adalah panjang) dan teks dekat yang menjelaskan hal itu (atau mencoba untuk melakukannya).
Jonathan Leffler

Deklarasi eksternal tentu saja tidak harus didefinisikan dalam unit terjemahan lain (dan umumnya tidak). Sebenarnya, deklarasi dan definisi bisa satu dan sama.
Ingat Monica,

40

Extern adalah kata kunci yang Anda gunakan untuk menyatakan bahwa variabel itu sendiri berada di unit terjemahan lain.

Jadi, Anda dapat memutuskan untuk menggunakan variabel dalam unit terjemahan dan kemudian mengaksesnya dari yang lain, kemudian di yang kedua Anda mendeklarasikannya sebagai eksternal dan simbol akan dipecahkan oleh tautan.

Jika Anda tidak mendeklarasikannya sebagai extern, Anda akan mendapatkan 2 variabel bernama sama tetapi tidak terkait sama sekali, dan kesalahan beberapa definisi variabel.


5
Dengan kata lain unit terjemahan di mana extern digunakan tahu tentang variabel ini, jenisnya dll. Dan karenanya memungkinkan kode sumber dalam logika yang mendasarinya untuk menggunakannya, tetapi tidak mengalokasikan variabel, unit terjemahan lain akan melakukan itu. Jika kedua unit terjemahan mendeklarasikan variabel secara normal, akan ada dua lokasi fisik untuk variabel tersebut, dengan referensi "salah" yang terkait dalam kode yang dikompilasi, dan dengan ambiguitas yang dihasilkan oleh penghubung.
mjv

26

Saya suka menganggap variabel eksternal sebagai janji yang Anda buat ke kompiler.

Ketika menemukan eksternal, kompiler hanya dapat mengetahui tipenya, bukan di mana "tinggal", sehingga tidak dapat menyelesaikan referensi.

Anda mengatakannya, "Percayalah padaku. Pada waktu tautan, referensi ini akan dapat diselesaikan."


Secara umum, deklarasi adalah janji bahwa nama akan dapat diselesaikan dengan tepat satu definisi pada waktu tautan. Extern mendeklarasikan variabel tanpa mendefinisikan.
Lie Ryan

18

extern memberitahu compiler untuk mempercayai Anda bahwa memori untuk variabel ini dideklarasikan di tempat lain, jadi ia tidak mencoba mengalokasikan / memeriksa memori.

Oleh karena itu, Anda dapat mengkompilasi file yang memiliki referensi ke eksternal, tetapi Anda tidak dapat menautkan jika memori itu tidak dideklarasikan di suatu tempat.

Berguna untuk variabel dan pustaka global, tetapi berbahaya karena tautan tidak mengetikkan centang.


Memori tidak dideklarasikan. Lihat jawaban atas pertanyaan ini: stackoverflow.com/questions/1410563 untuk lebih jelasnya.
sbi

15

Menambahkan externmengubah definisi variabel menjadi deklarasi variabel . Lihat utas ini tentang apa perbedaan antara deklarasi dan definisi.


Apa perbedaan antara int foodan extern int foo(ruang lingkup file)? Keduanya deklarasi, bukan?

@ user14284: Keduanya adalah deklarasi hanya dalam arti bahwa setiap definisi juga merupakan deklarasi. Tapi saya terhubung dengan penjelasan tentang ini. ("Lihat utas ini tentang apa perbedaan antara deklarasi dan definisi.") Mengapa Anda tidak mengikuti tautannya dan membaca saja?
sbi

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

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

Deklarasi tidak akan mengalokasikan memori (variabel harus ditentukan untuk alokasi memori) tetapi definisi akan. Ini hanyalah tampilan sederhana dari kata kunci eksternal karena jawaban yang lain benar-benar hebat.


11

Interpretasi extern yang benar adalah bahwa Anda memberi tahu sesuatu kepada kompiler. Anda memberi tahu kompiler bahwa, meskipun tidak ada sekarang, variabel yang dideklarasikan entah bagaimana akan ditemukan oleh linker (biasanya di objek lain (file)). Linker kemudian akan menjadi orang yang beruntung untuk menemukan segalanya dan menyatukannya, apakah Anda memiliki deklarasi eksternal atau tidak.


8

Dalam C variabel di dalam file katakan example.c diberikan lingkup lokal. Compiler mengharapkan bahwa variabel akan memiliki definisi di dalam file example.c yang sama dan ketika tidak menemukan yang sama, itu akan melempar kesalahan. Fungsi di sisi lain memiliki lingkup global default. Dengan demikian Anda tidak perlu menyebutkan secara eksplisit ke kompiler "lihat Bung ... Anda mungkin menemukan definisi fungsi ini di sini". Untuk fungsi termasuk file yang berisi deklarasi sudah cukup. (File yang sebenarnya Anda sebut file header). Sebagai contoh, pertimbangkan 2 file berikut:
example.c

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

contoh1.c

int a = 5;

Sekarang ketika Anda mengkompilasi dua file bersama, menggunakan perintah berikut:

langkah 1) cc -o ex example.c example1.c langkah 2) ./ ex

Anda mendapatkan output berikut: Nilai a adalah <5>


8

Implementasi GCC ELF Linux

Jawaban lain telah membahas sisi penggunaan bahasa, jadi sekarang mari kita lihat bagaimana penerapannya dalam implementasi ini.

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

Kompilasi dan dekompilasi:

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

Output berisi:

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

The System V ABI Perbarui ELF spek "Symbol Table" bab menjelaskan:

SHN_UNDEF Indeks tabel bagian ini berarti simbol tidak terdefinisi. Ketika editor tautan menggabungkan file objek ini dengan yang mendefinisikan simbol yang ditunjukkan, referensi file ini ke simbol akan ditautkan ke definisi yang sebenarnya.

yang pada dasarnya adalah perilaku yang diberikan oleh standar C kepada externvariabel.

Mulai sekarang, itu adalah tugas dari linker untuk membuat program akhir, tetapi externinformasi tersebut telah diekstraksi dari kode sumber ke file objek.

Diuji pada GCC 4.8.

C ++ 17 variabel sebaris

Dalam C ++ 17, Anda mungkin ingin menggunakan variabel inline alih-alih yang eksternal, karena mereka mudah digunakan (dapat didefinisikan sekali saja pada header) dan lebih kuat (support constexpr). Lihat: Apa arti 'const static' dalam C dan C ++?


3
Ini bukan pilihan saya, jadi saya tidak tahu. Namun, saya akan mengajukan pendapat. Meskipun melihat output readelfatau nmdapat membantu, Anda belum menjelaskan dasar-dasar bagaimana memanfaatkannya extern, atau menyelesaikan program pertama dengan definisi aktual. Kode Anda bahkan tidak digunakan notExtern. Ada masalah nomenklatur, juga: meskipun notExterndidefinisikan di sini daripada dideklarasikan dengan extern, ini adalah variabel eksternal yang dapat diakses oleh file sumber lain jika unit terjemahan tersebut berisi deklarasi yang sesuai (yang perlu extern int notExtern;!).
Jonathan Leffler

1
@JonathanLeffler terima kasih atas umpan baliknya! Rekomendasi perilaku standar dan penggunaan sudah dilakukan di jawaban lain, jadi saya memutuskan untuk menunjukkan implementasi sedikit karena itu benar-benar membantu saya memahami apa yang sedang terjadi. Tidak menggunakan notExternitu jelek, memperbaikinya. Tentang nomenklatur, beri tahu saya jika Anda memiliki nama yang lebih baik. Tentu saja itu bukan nama yang bagus untuk program yang sebenarnya, tapi saya pikir itu cocok dengan peran didaktik di sini.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Mengenai nama, bagaimana global_defdengan variabel yang didefinisikan di sini, dan extern_refuntuk variabel yang ditentukan dalam beberapa modul lainnya? Apakah mereka akan memiliki simetri yang jelas? Anda masih berakhir dengan int extern_ref = 57;atau sesuatu seperti itu di file di mana itu didefinisikan, sehingga namanya tidak cukup ideal, tetapi dalam konteks file sumber tunggal, itu adalah pilihan yang masuk akal. Memiliki extern int global_def;header bukan masalah, menurut saya. Semuanya terserah Anda, tentu saja.
Jonathan Leffler

7

kata kunci extern digunakan dengan variabel untuk identifikasi sebagai variabel global.

Ini juga menyatakan bahwa Anda dapat menggunakan variabel yang dideklarasikan menggunakan kata kunci eksternal dalam file apa pun meskipun itu dinyatakan / didefinisikan dalam file lain.


5

extern memungkinkan satu modul program Anda untuk mengakses variabel global atau fungsi yang dinyatakan dalam modul lain dari program Anda. Anda biasanya memiliki variabel eksternal yang dinyatakan dalam file header.

Jika Anda tidak ingin program mengakses variabel atau fungsi Anda, Anda menggunakan staticyang memberitahu kompiler bahwa variabel atau fungsi ini tidak dapat digunakan di luar modul ini.


5

extern hanya berarti suatu variabel didefinisikan di tempat lain (misalnya, dalam file lain).


4

Pertama, externkata kunci tidak digunakan untuk mendefinisikan variabel; melainkan digunakan untuk mendeklarasikan variabel. Bisa saya katakan externadalah kelas penyimpanan, bukan tipe data.

externdigunakan untuk memberi tahu file C lainnya atau komponen eksternal tahu variabel ini sudah ditentukan di suatu tempat. Contoh: jika Anda sedang membangun perpustakaan, tidak perlu mendefinisikan variabel global secara wajib di suatu tempat di perpustakaan itu sendiri. Perpustakaan akan dikompilasi secara langsung, tetapi saat menautkan file, itu memeriksa definisi.


3

externdigunakan sehingga satu first.cfile dapat memiliki akses penuh ke parameter global di second.cfile lain .

The externdapat dinyatakan dalam first.cfile atau di salah satu file header first.ctermasuk.


3
Perhatikan bahwa externdeklarasi harus dalam header, bukan di first.c, sehingga jika jenisnya berubah, deklarasi akan berubah juga. Juga, header yang menyatakan variabel harus dimasukkan oleh second.cuntuk memastikan bahwa definisi konsisten dengan deklarasi. Deklarasi di header adalah lem yang menyatukan semuanya; itu memungkinkan file dikompilasi secara terpisah tetapi memastikan mereka memiliki pandangan yang konsisten dari jenis variabel global.
Jonathan Leffler

2

Dengan xc8 Anda harus berhati-hati dalam mendeklarasikan variabel sebagai tipe yang sama di setiap file yang Anda bisa, secara keliru, mendeklarasikan sesuatu intdalam satu file danchar mengatakan dalam lain. Ini dapat menyebabkan korupsi variabel.

Masalah ini diselesaikan dengan elegan dalam forum microchip sekitar 15 tahun yang lalu / * Lihat "http: www.htsoft.com" / / "forum / all / showflat.php / Cat / 0 / Number / 18766 / an / 0 / halaman / 0 # 18766 "

Tetapi tautan ini sepertinya tidak lagi berfungsi ...

Jadi saya akan segera mencoba menjelaskannya; buat file bernama global.h.

Di dalamnya mendeklarasikan sebagai berikut

#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

Sekarang di file main.c

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

Ini berarti di main.c variabel akan dideklarasikan sebagai unsigned char .

Sekarang di file lain cukup dengan global.h akan menyatakannya sebagai extern untuk file itu .

extern unsigned char testing_mode;

Tapi itu akan dinyatakan dengan benar sebagai unsigned char .

Posting forum lama mungkin menjelaskan ini sedikit lebih jelas. Tapi ini adalah potensi nyata gotchaketika menggunakan kompiler yang memungkinkan Anda untuk mendeklarasikan variabel dalam satu file dan kemudian mendeklarasikannya sebagai jenis berbeda di lain. Masalah yang terkait dengan itu adalah jika Anda mengatakan mendeklarasikan testing_mode sebagai int di file lain itu akan berpikir itu adalah 16 bit var dan menimpa beberapa bagian lain dari ram, berpotensi merusak variabel lain. Sulit di-debug!


0

Solusi yang sangat singkat yang saya gunakan untuk memungkinkan file header berisi referensi eksternal atau implementasi aktual suatu objek. File yang sebenarnya hanya berisi objek#define GLOBAL_FOO_IMPLEMENTATION . Kemudian ketika saya menambahkan objek baru ke file ini muncul di file itu juga tanpa saya harus menyalin dan menempelkan definisi.

Saya menggunakan pola ini di beberapa file. Jadi untuk menjaga segala sesuatunya mungkin, saya hanya menggunakan makro GLOBAL tunggal di setiap header. Header saya terlihat seperti ini:

//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
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.