Jawaban:
Penggunaan extern
hanya relevan ketika program yang Anda buat terdiri dari beberapa file sumber yang dihubungkan bersama, di mana beberapa variabel didefinisikan, misalnya, dalam file sumber file1.c
perlu 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 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.c
dan file2.c
:
extern int global_variable; /* Declaration of the variable */
#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++; }
#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 extern
di depan deklarasi fungsi di header untuk konsistensi - untuk mencocokkan extern
di depan deklarasi variabel dalam header. Banyak orang memilih untuk tidak menggunakan extern
deklarasi fungsi di depan; kompiler tidak peduli - dan pada akhirnya, saya juga tidak selama Anda konsisten, setidaknya dalam file sumber.
extern void use_it(void);
extern int increment(void);
#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
kegunaan prog1.c
, file1.c
, file2.c
, file3.h
dan prog1.h
.File prog1.mk
ini prog1
hanya untuk makefile . Ini akan bekerja dengan sebagian besar versi yang make
diproduksi sejak pergantian milenium. Itu tidak terikat khusus untuk GNU Make.
# 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}
Aturan untuk dilanggar oleh para ahli saja, dan hanya dengan alasan yang bagus:
File header hanya berisi extern
deklarasi variabel - tidak pernah static
atau 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 extern
deklarasi 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.
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:
#include "prog2.h"
long l; /* Do not do this in portable code */
void inc(void) { l++; }
#include "prog2.h"
long l; /* Do not do this in portable code */
void dec(void) { l--; }
#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).
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
sizeof
atau_Alignof
yang 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 l
sebagai double
bukan sebagai a long
, tipe linker tidak aman C mungkin tidak akan menemukan ketidakcocokan. Jika Anda menggunakan mesin dengan 64-bit long
dan double
, Anda bahkan tidak akan mendapatkan peringatan; pada mesin dengan 32-bit long
dan 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
:
extern void dec(void);
extern void put(void);
extern void inc(void);
#include "prog2.h"
#include <stdio.h>
int main(void)
{
inc();
put();
dec();
put();
dec();
put();
}
prog2
kegunaan prog2.c
, file10.c
, file11.c
, file12.c
, prog2.h
.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.
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.
c int some_var; /* Do not do this in a header!!! */
Catatan 1: jika header mendefinisikan variabel tanpa extern
kata 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.
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.
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.
Gunakan teknik tajuk yang saya tunjukkan pertama kali. Ia bekerja dengan andal dan di mana-mana. Perhatikan, khususnya, bahwa header yang menyatakan global_variable
disertakan 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.
Jika Anda bukan seorang programmer C yang berpengalaman, Anda mungkin harus berhenti membaca di sini.
Penambahan Utama Terlambat
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
:
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern
#endif /* DEFINE_VARIABLES */
EXTERN int global_variable;
#define DEFINE_VARIABLES
#include "file3a.h" /* Variable defined - but not initialized */
#include "prog3.h"
int increment(void) { return global_variable++; }
#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
:
extern void use_it(void);
extern int increment(void);
#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
kegunaan prog3.c
, file1a.c
, file2a.c
, file3a.h
, prog3.h
.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.)
#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 #if
dan #else
blokir, memperbaiki bug yang diidentifikasi oleh
Denis Kniazhev
#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; }
#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 INITIALIZER
adalah { 41
dan 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.h
disertakan (bukan fileba.h
) per
Denis Kniazhev
Dua file selanjutnya melengkapi sumber untuk prog4
:
extern int increment(void);
extern int oddball_value(void);
extern void use_them(void);
#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
kegunaan prog4.c
, file1b.c
, file2b.c
, prog4.h
, file3b.h
.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.h
menyertakan file3b.h
definisi jenis yang tidak ditampilkan, dan file1b.c
perlu menggunakan tajuk file4b.h
dan 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.h
sebelum memasukkan file3b.h
untuk menghasilkan definisi, tetapi penjaga header normal pada file3b.h
akan mencegah header dimasukkan kembali.
Jadi, Anda perlu memasukkan badan file3b.h
paling 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).
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.h
untuk menentukan jenis (terutama, struct oddball
jenis 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.c
dan file6c.c
secara langsung sertakan header file2c.h
beberapa 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:
Header yang mendefinisikan atau mendeklarasikan variabel global mungkin tidak dengan sendirinya mendefinisikan tipe apa pun.
Segera sebelum Anda menyertakan header yang harus mendefinisikan variabel, Anda mendefinisikan DEFINE_VARIABLES makro.
Header yang mendefinisikan atau mendeklarasikan variabel memiliki konten yang distilisasi.
#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#define INITIALIZE(...) = __VA_ARGS__
#else
#define EXTERN extern
#define INITIALIZE(...) /* nothing */
#endif /* DEFINE_VARIABLES */
#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 */
/* 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 */
#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; }
#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;
}
#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; }
#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
, prog6
dan prog7
:
#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
kegunaan prog5.c
, file3c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog6
kegunaan prog5.c
, file5c.c
, file4c.c
, file1c.h
, file2c.h
, external.h
.
prog7
kegunaan 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.h
ke 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 #define
dan #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"
#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_VARIABLES
di file2d.h
).
/* 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; }
/* 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 */
/* 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 prog8
dan prog9
:
#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;
}
#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
kegunaan prog8.c
, file7c.c
, file9c.c
.
prog9
kegunaan prog8.c
, file8c.c
, file9c.c
.
Namun, masalahnya relatif tidak mungkin terjadi dalam praktik, terutama jika Anda mengikuti saran standar
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.h
dan 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.c
dan prog8.c
adalah nama salah satu tajuk yang disertakan. Dimungkinkan untuk mengatur ulang kode sehingga main()
fungsinya tidak diulangi, tetapi akan menyembunyikan lebih banyak daripada yang diungkapkannya.)
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
,.
Sebuah extern
variabel 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 .c
file test1.c
dan test2.c
. Jika anda mendefinisikan variabel global int test1_var;
di test1.c
dan Anda ingin mengakses variabel ini dalam 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
extern int test1_var;
to int test1_var;
, linker (gcc 5.4.0) masih lewat. Jadi, apakah extern
benar-benar dibutuhkan dalam kasus ini?
extern
adalah 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).
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.
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."
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.
Menambahkan extern
mengubah definisi variabel menjadi deklarasi variabel . Lihat utas ini tentang apa perbedaan antara deklarasi dan definisi.
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.
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.
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>
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 extern
variabel.
Mulai sekarang, itu adalah tugas dari linker untuk membuat program akhir, tetapi extern
informasi 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 ++?
readelf
atau nm
dapat 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 notExtern
didefinisikan 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;
!).
notExtern
itu 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.
global_def
dengan variabel yang didefinisikan di sini, dan extern_ref
untuk 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.
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 static
yang memberitahu kompiler bahwa variabel atau fungsi ini tidak dapat digunakan di luar modul ini.
Pertama, extern
kata kunci tidak digunakan untuk mendefinisikan variabel; melainkan digunakan untuk mendeklarasikan variabel. Bisa saya katakan extern
adalah kelas penyimpanan, bukan tipe data.
extern
digunakan 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.
extern
digunakan sehingga satu first.c
file dapat memiliki akses penuh ke parameter global di second.c
file lain .
The extern
dapat dinyatakan dalam first.c
file atau di salah satu file header first.c
termasuk.
extern
deklarasi harus dalam header, bukan di first.c
, sehingga jika jenisnya berubah, deklarasi akan berubah juga. Juga, header yang menyatakan variabel harus dimasukkan oleh second.c
untuk 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.
Dengan xc8 Anda harus berhati-hati dalam mendeklarasikan variabel sebagai tipe yang sama di setiap file yang Anda bisa, secara keliru, mendeklarasikan sesuatu int
dalam 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 gotcha
ketika 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!
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