Pertanyaan Bagaimana cara menggunakan eksternal untuk berbagi variabel antara file sumber?


Saya tahu bahwa variabel global dalam C terkadang memiliki extern kata kunci. Apa itu extern variabel? Seperti apa deklarasinya? Apa ruang lingkupnya?

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


810
2017-09-16 14:08


asal


Jawaban:


Menggunakan extern hanya relevansi ketika program yang Anda buat terdiri dari beberapa file sumber yang dihubungkan bersama, di mana beberapa variabel yang ditentukan, misalnya, dalam file sumber file1.c perlu direferensikan dalam file sumber lain, seperti file2.c.

Ini penting untuk mengerti perbedaan antara mendefinisikan Sebuah variabel dan menyatakan Sebuah variabel:

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

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

Cara terbaik untuk mendeklarasikan dan mendefinisikan variabel global

Cara yang bersih dan andal untuk menyatakan dan mendefinisikan variabel global adalah dengan menggunakan file header yang berisi extern  pernyataan dari variabel.

Header disertakan oleh satu file sumber yang mendefinisikan variabel dan oleh semua file sumber yang mereferensikan variabel. Untuk setiap program, satu file sumber (dan hanya satu file sumber) yang mendefinisikan variabel. Demikian pula, satu file header (dan hanya satu file header) harus menyatakan variabel. File header sangat penting; itu memungkinkan pemeriksaan silang antara TU independen (unit terjemahan - pikirkan file sumber) dan pastikan konsistensi.

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

Itulah cara terbaik untuk mendeklarasikan dan mendefinisikan variabel global.


Dua file berikutnya melengkapi sumbernya prog1:

Program lengkap ditampilkan menggunakan fungsi, sehingga deklarasi fungsi merayap masuk Baik C99 dan C11 membutuhkan fungsi untuk dideklarasikan atau ditentukan sebelum mereka digunakan (sedangkan C90 tidak, untuk alasan yang baik). Saya menggunakan kata kunci extern di depan deklarasi fungsi di header untuk konsistensi - untuk mencocokkan extern di depan variabel deklarasi di header. Banyak orang memilih untuk tidak menggunakannya extern di depan fungsi deklarasi; compiler tidak peduli - dan 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;
}
  • prog1 menggunakan prog1.c, file1.c, file2.c, file3.h dan prog1.h.

Berkas prog1.mk adalah makefile untuk prog1 hanya. Ini akan berfungsi dengan sebagian besar versi make diproduksi sejak sekitar belokan dari 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}

Panduan

Aturan harus dipatahkan oleh para ahli saja, dan hanya dengan alasan yang bagus:

  • File header hanya berisi extern deklarasi variabel - tidak pernah static atau definisi variabel yang tidak memenuhi syarat.
  • Untuk setiap variabel yang diberikan, hanya satu file header yang menyatakannya (SPOT - Single Point of Truth).
  • File sumber tidak pernah mengandung extern deklarasi variabel - file sumber selalu menyertakan header (tunggal) yang mendeklarasikannya.
  • Untuk setiap variabel yang diberikan, tepat satu file sumber mendefinisikan variabel, sebaiknya inisialisasi juga. (Meskipun tidak perlu menginisialisasi secara eksplisit ke nol, itu tidak membahayakan dan dapat melakukan sesuatu yang baik, karena hanya ada satu definisi yang diinisialisasi dari yang khusus variabel global dalam suatu program).
  • File sumber yang menentukan variabel juga menyertakan tajuk ke memastikan bahwa definisi dan deklarasi konsisten.
  • Suatu fungsi seharusnya tidak perlu mendeklarasikan variabel yang digunakan extern.
  • Hindari variabel global bila memungkinkan - gunakan fungsi sebagai gantinya.

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

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

Bukan cara yang baik untuk mendefinisikan variabel global

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

file10.c

#include "prog2.h"

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

void inc(void) { i++; }

file11.c

#include "prog2.h"

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

void dec(void) { i--; }

file12.c

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

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

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

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

J.2 Perilaku tidak terdefinisi

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

§6.9 Definisi eksternal ¶5

Sebuah definisi eksternal adalah deklarasi eksternal yang juga merupakan   definisi fungsi (selain definisi sebaris) atau   obyek.   Jika pengenal yang dideklarasikan dengan tautan eksternal digunakan dalam   ekspresi (selain sebagai bagian dari operan a sizeof atau    _Alignof operator yang hasilnya adalah konstanta integer), di suatu tempat di   seluruh program akan ada satu definisi eksternal untuk   pengenal; jika tidak, tidak akan ada lebih dari itu   satu.161)

161) Jadi, jika identifier dideklarasikan dengan hubungan eksternal   tidak digunakan dalam ekspresi, tidak perlu ada definisi eksternal untuk   saya t.

Namun, standar C juga mencantumkannya dalam Lampiran J yang informatif sebagai salah satu dari itu Ekstensi umum.

J.5.11 Beberapa definisi eksternal

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

Karena teknik ini tidak selalu didukung, sebaiknya dihindari menggunakannya, terutama jika kode Anda harus portabel. Dengan menggunakan teknik ini, Anda juga dapat berakhir dengan tipe yang tidak disengaja meninju. Jika salah satu file dinyatakan i sebagai double bukannya sebagai int, Tipe-unsafe linker C mungkin tidak akan menemukan ketidakcocokan. Jika Anda menggunakan mesin dengan 64-bit int dan double, Anda bahkan tidak akan dapatkan peringatan; pada mesin dengan 32-bit int dan 64-bit double, Anda akan mungkin mendapatkan peringatan tentang ukuran yang berbeda - penghubung akan gunakan ukuran terbesar, persis seperti program Fortran yang akan mengambil ukuran terbesar dari setiap blok umum.


Dua file berikutnya melengkapi sumbernya 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 menggunakan prog2.c, file10.c, file11.c, file12.c, prog2.h.

PERINGATAN

Sebagaimana dicatat dalam komentar di sini, dan seperti yang dinyatakan dalam jawaban saya untuk serupa pertanyaan, menggunakan banyak definisi untuk variabel global mengarah ke perilaku tidak terdefinisi (J.2; §6.9), yang merupakan cara standar untuk mengatakan "apa pun bisa terjadi". Salah satu hal yang bisa terjadi adalah program itu berperilaku seperti Anda mengharapkan; dan J.5.11 mengatakan, kira-kira, "Anda mungkin lebih beruntung lebih sering dari yang layak Anda dapatkan ". Tetapi sebuah program yang bergantung pada beberapa definisi dari variabel eksternal - dengan atau tanpa kata kunci 'eksternal' eksplisit - tidak ketat menyesuaikan program dan tidak dijamin bekerja di mana-mana. Equivalently: mengandung bug yang mungkin atau mungkin tidak muncul dengan sendirinya.

Melanggar pedoman

Tentu saja ada banyak cara di mana pedoman ini bisa dipatahkan. Terkadang, mungkin ada alasan bagus untuk melanggar pedoman, tapi acara semacam itu sangat tidak biasa.

faulty_header.h

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

Catatan 1: jika header mendefinisikan variabel tanpa extern kata kunci, kemudian setiap file yang menyertakan header menciptakan definisi sementara dari variabel. Seperti yang disebutkan sebelumnya, ini akan sering berhasil, tetapi standar C tidak menjamin bahwa itu akan berhasil.

broken_header.h

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

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

seldom_correct.h

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

Catatan 3: jika header mendefinisikan variabel statis (dengan atau tanpa inisialisasi), kemudian setiap file sumber berakhir dengan privasinya sendiri versi variabel 'global'.

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


Ringkasan

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

Kekhawatiran serupa muncul dengan mendeklarasikan dan mendefinisikan fungsi - aturan analog berlaku. Tapi pertanyaannya adalah tentang variabel khusus, jadi saya sudah menyimpannya hanya menjawab variabel.

Akhir Jawaban Asli

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


Akhir Penambahan Utama

Menghindari Duplikasi Kode

Salah satu perhatian yang kadang (dan sah) mengangkat tentang 'Deklarasi dalam header, definisi dalam mekanisme sumber' dijelaskan di sini adalah bahwa ada dua file yang harus tetap disinkronkan - header dan sumbernya. Ini biasanya ditindaklanjuti dengan observasi bahwa a makro dapat digunakan sehingga header melayani tugas ganda - biasanya mendeklarasikan variabel, tetapi ketika makro tertentu diatur sebelum header disertakan, itu mendefinisikan variabel sebagai gantinya.

Kekhawatiran lain dapat berupa bahwa variabel perlu didefinisikan di masing-masing sejumlah 'program utama'. Ini biasanya merupakan kekhawatiran palsu; kamu dapat dengan mudah memperkenalkan file sumber C untuk menentukan variabel dan tautan file objek yang dihasilkan dengan masing-masing program.

Skema tipikal berfungsi seperti ini, menggunakan variabel global asli 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 berikutnya melengkapi sumbernya 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 menggunakan 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 argumen variabel daftar untuk makro, Anda dapat menentukan makro untuk mendukung inisialisasi juga. (Dengan C89 dan tidak ada dukungan untuk daftar argumen variabel dalam makro, tidak ada cara mudah untuk menangani initializers sewenang-wenang panjang.)

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 isi #if dan #else blok, 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 seperti biasanya menulis, tetapi itu menggambarkan maksudnya. Argumen pertama untuk yang kedua doa dari INITIALIZER aku s { 41 dan argumen yang tersisa (tunggal dalam contoh ini) adalah 43 }. Tanpa C99 atau dukungan serupa untuk daftar argumen variabel untuk makro, initializers yang perlu mengandung koma yang sangat bermasalah.

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


Dua file berikutnya melengkapi sumbernya 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 menggunakan prog4.c, file1b.c, file2b.c, prog4.h, file3b.h.

Header Guards

Setiap header harus dilindungi terhadap reinklusi, sehingga jenis itu definisi (tipe enum, struct atau union, atau typedefs umumnya) tidak menyebabkan masalah. Teknik standar adalah membungkus tubuh tajuk di header guard seperti:

#ifndef FILE3B_H_INCLUDED
#define FILE3B_H_INCLUDED

...contents of header...

#endif /* FILE3B_H_INCLUDED */

Header mungkin termasuk dua kali secara tidak langsung. Misalnya, jika file4b.h termasuk file3b.h untuk definisi jenis yang tidak ditampilkan, dan file1b.cperlu menggunakan kedua tajuk file4b.h dan file3b.h, kemudian Anda memiliki beberapa masalah yang lebih sulit untuk diselesaikan. Jelas, Anda dapat merevisi daftar tajuk untuk menyertakan saja file4b.h. Namun, Anda mungkin tidak menyadari ketergantungan internal - dan kode seharusnya, idealnya, terus bekerja.

Lebih lanjut, itu mulai menjadi rumit karena Anda mungkin memasukkan file4b.h sebelum termasuk file3b.h untuk menghasilkan definisi, tetapi normal penjaga header file3b.h akan mencegah sundulan dimasukkan kembali.

Jadi, Anda perlu menyertakan tubuh file3b.h paling banyak sekali untuk deklarasi, dan paling banyak sekali untuk definisi, tetapi Anda mungkin membutuhkan keduanya dalam unit terjemahan tunggal (TU - kombinasi file sumber dan header yang digunakannya).

Beberapa inklusi dengan definisi variabel

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

  • external.h untuk definisi makro EXTERN, dll.
  • file1c.h untuk menentukan jenis (terutama, struct oddball, tipe dari 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 menentukan variabel global.
  • file6c.c yang menunjukkan bahwa Anda dapat menentukan dan kemudian (berusaha) mendeklarasikan variabel global.

Dalam contoh-contoh ini, file5c.c dan file6c.c langsung sertakan tajuk file2c.h beberapa kali, tetapi itu adalah cara paling sederhana untuk menunjukkan bahwa mekanisme bekerja. Itu berarti bahwa jika header secara tidak langsung dimasukkan dua kali, itu juga akan aman.

Pembatasan untuk ini untuk bekerja adalah:

  1. Header mendefinisikan atau mendeklarasikan variabel global mungkin tidak sendiri tentukan jenis apa pun.
  2. Segera sebelum Anda menyertakan header yang harus mendefinisikan variabel, Anda mendefinisikan DEFINE_VARIABLES makro.
  3. Header mendefinisikan atau mendeklarasikan variabel memiliki isi bergaya.

external.h

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

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

file1c.h

#ifndef FILE1C_H_INCLUDED
#define FILE1C_H_INCLUDED

struct oddball
{
    int a;
    int b;
};

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

#endif /* FILE1C_H_INCLUDED */

file2c.h

/* Standard prologue */
#if defined(DEFINE_VARIABLES) && !defined(FILE2C_H_DEFINITIONS)
#undef FILE2C_H_INCLUDED
#endif

#ifndef FILE2C_H_INCLUDED
#define FILE2C_H_INCLUDED

#include "external.h"   /* Support macros EXTERN, INITIALIZE */
#include "file1c.h"     /* Type definition for struct oddball */

#if !defined(DEFINE_VARIABLES) || !defined(FILE2C_H_DEFINITIONS)

/* Global variable declarations / definitions */
EXTERN int global_variable INITIALIZE(37);
EXTERN struct oddball oddball_struct INITIALIZE({ 41, 43 });

#endif /* !DEFINE_VARIABLES || !FILE2C_H_DEFINITIONS */

/* Standard epilogue */
#ifdef DEFINE_VARIABLES
#define FILE2C_H_DEFINITIONS
#endif /* DEFINE_VARIABLES */

#endif /* FILE2C_H_INCLUDED */

file3c.c

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file4c.c

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

void use_them(void)
{
    printf("Global variable: %d\n", global_variable++);
    oddball_struct.a += global_variable;
    oddball_struct.b -= global_variable / 2;
}

file5c.c

#include "file2c.h"     /* Declare variables */

#define DEFINE_VARIABLES
#include "file2c.h"  /* Variables now defined and initialized */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

file6c.c

#define DEFINE_VARIABLES
#include "file2c.h"     /* Variables now defined and initialized */

#include "file2c.h"     /* Declare variables */

int increment(void) { return global_variable++; }
int oddball_value(void) { return oddball_struct.a + oddball_struct.b; }

File sumber berikutnya melengkapi sumber (menyediakan program utama) untuk prog5, prog6 dan prog7:

prog5.c

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

int main(void)
{
    use_them();
    global_variable += 19;
    use_them();
    printf("Increment: %d\n", increment());
    printf("Oddball:   %d\n", oddball_value());
    return 0;
}
  • prog5 menggunakan prog5.c, file3c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog6 menggunakan prog5.c, file5c.c, file4c.c, file1c.h, file2c.h, external.h.
  • prog7 menggunakan prog5.c, file6c.c, file4c.c, file1c.h, file2c.h, external.h.

Skema ini menghindari sebagian besar masalah. Anda hanya mengalami masalah jika a tajuk yang mendefinisikan variabel (seperti file2c.h) disertakan oleh tajuk lain (misalnya file7c.h) yang mendefinisikan variabel. Tidak ada cara mudah di sekitar itu selain "jangan lakukan itu".

Anda dapat menyelesaikan sebagian masalah dengan merevisi file2c.h ke 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 'seharusnya header disertakan #undef DEFINE_VARIABLES? ' Jika Anda menghilangkan itu dari header dan membungkus setiap penetapan doa dengan #define dan #undef:

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

dalam kode sumber (sehingga header tidak pernah mengubah nilai DEFINE_VARIABLES), maka Anda harus bersih. Itu hanya mengganggu harus ingat untuk menulis baris tambahan. Sebuah alternatif mungkin:

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

externdef.h

/*
** This header must not contain header guards (like <assert.h> must not).
** Each time it is included, the macro HEADER_DEFINING_VARIABLES should
** be defined with the name (in quotes - or possibly angle brackets) of
** the header to be included that defines variables when the macro
** DEFINE_VARIABLES is defined.  See also: external.h (which uses
** DEFINE_VARIABLES and defines macros EXTERN and INITIALIZE
** appropriately).
**
** #define HEADER_DEFINING_VARIABLES "file2c.h"
** #include "externdef.h"
*/

#if defined(HEADER_DEFINING_VARIABLES)
#define DEFINE_VARIABLES
#include HEADER_DEFINING_VARIABLES
#undef DEFINE_VARIABLES
#undef HEADER_DEFINING_VARIABLES
#endif /* HEADER_DEFINING_VARIABLES */

Ini semakin sedikit rumit, tetapi tampaknya aman (menggunakan file2d.h, dengan tidak #undef DEFINE_VARIABLES dalam 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 berikutnya melengkapi sumbernya prog8 dan 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 menggunakan prog8.c, file7c.c, file9c.c.
  • prog9 menggunakan prog8.c, file8c.c, file9c.c.

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

Hindari variabel global


Apakah eksposisi ini merindukan sesuatu?

Pengakuan: Skema 'menghindari kode duplikat' yang diuraikan di sini adalah dikembangkan karena masalah mempengaruhi beberapa kode yang saya kerjakan (tetapi tidak memilikinya), dan merupakan kekhawatiran picik dengan skema yang digariskan di bagian pertama jawabannya. Namun, skema asli meninggalkan Anda hanya dengan dua tempat untuk memodifikasi untuk menjaga definisi dan deklarasi variabel disinkronkan, yang merupakan langkah maju yang besar setelah memiliki variabel eksternal deklarasi yang tersebar di seluruh basis kode (yang benar-benar penting ketika ada ribuan file secara total). Namun, kode di file dengan nama fileNc.[ch] (plus external.h dan externdef.h) menunjukkan bahwa itu dapat dibuat untuk bekerja. Jelas, itu tidak akan sulit membuat skrip generator header untuk memberi Anda template standar untuk file header yang mendefinisikan dan mendeklarasikan variabel.

NB Ini adalah program mainan dengan kode yang hampir tidak cukup untuk membuatnya sedikit menarik. Ada pengulangan dalam contoh itu dapat dihapus, tetapi tidak untuk menyederhanakan penjelasan pedagogis. (Misalnya: perbedaan antara prog5.c dan prog8.c adalah namanya dari salah satu header yang disertakan. Itu mungkin untuk mengatur ulang kode sehingga main() fungsi tidak diulang, tetapi itu akan menyembunyikan lebih dari yang diungkapkan.)


1482
2017-09-16 14:37



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

Katakanlah Anda punya dua .c-filter test1.c dan test2.c. Jika Anda mendefinisikan variabel global int test1_var; di test1.c dan Anda ingin mengakses variabel ini di test2.c Anda 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

108
2017-09-16 14:12



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 menyatakan sebagai ekstern dan simbol akan diselesaikan oleh penaut.

Jika Anda tidak menyatakannya sebagai ekstern Anda akan mendapatkan 2 variabel yang bernama sama tetapi tidak terkait sama sekali, dan kesalahan beberapa definisi dari variabel.


34
2017-09-16 14:11



Saya suka memikirkan variabel eksternal sebagai janji yang Anda buat ke compiler.

Ketika menghadapi sebuah ekstern, kompilator hanya dapat mengetahui tipenya, bukan di mana ia "hidup", sehingga tidak dapat menyelesaikan referensi.

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


23
2017-09-16 14:50



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

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

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


17
2017-09-16 14:18



Menambahkan sebuah extern ternyata variabel definisi menjadi variabel pernyataan. Lihat utas ini seperti apa perbedaan antara deklarasi dan definisi.


15
2017-09-16 14:16



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


11
2018-06-20 23:43