External linkage adalah properti simbol (variabel atau fungsi) yang membuat simbol tersebut dapat dilihat dan diakses oleh translation unit lain saat linking. Tanpa external linkage, setiap file .c harus mendefinisikan ulang simbol yang dibutuhkan—suatu hal yang tidak praktis di proyek berskala menengah. Pada chapter ini kita akan memahami konsep linkage di C, mempraktikkan deklarasi extern di header untuk variabel global lintas file, dan melihat bagaimana linker menyatukan simbol yang didefinisikan di satu .c dengan referensi di .c lain.
Konsep Linkage di C #
Standar ISO/IEC 9899 §6.2.2 mendefinisikan tiga jenis linkage: external, internal, dan no linkage. External linkage berarti simbol dapat dirujuk dari translation unit lain melalui linker. Internal linkage berarti simbol hanya terlihat di dalam translation unit tempat ia didefinisikan. No linkage berarti simbol bersifat lokal (mis. variabel lokal fungsi).
Secara default, fungsi dan variabel global yang didefinisikan di tingkat file memiliki external linkage. Artinya, fungsi int faktorial(int n) yang didefinisikan di math.c otomatis terlihat oleh linker sebagai simbol publik, dan dapat dipanggil dari main.c—cukup dengan deklarasi int faktorial(int n); di header atau langsung di main.c. Keyword extern pada deklarasi variabel adalah penanda eksplisit bahwa variabel tersebut didefinisikan di translation unit lain.
Perbedaan penting antara fungsi dan variabel: deklarasi fungsi (tanpa badan) secara implisit berperilaku extern—kita tidak perlu menulis keyword extern di depan. Untuk variabel, deklarasi int x; di header sebenarnya adalah tentative definition yang bisa menyebabkan multiple definition saat di-include oleh beberapa file .c. Oleh karena itu, deklarasi variabel lintas file wajib diawali extern di header, dan definisi aktual (int x = ...; atau int x;) hanya boleh muncul di satu file .c.
Demonstrasi: Variabel Global Lintas File #
Mari kita bangun proyek kecil dengan file config.c yang mendefinisikan beberapa variabel global publik, dan main.c yang mengakses variabel tersebut. Header config.h berisi deklarasi extern sebagai jembatan antara kedua file.
File config.h:
#ifndef CONFIG_H
#define CONFIG_H
/* Deklarasi extern: variable didefinisikan di config.c */
extern int versi_major;
extern int versi_minor;
extern const char *nama_aplikasi;
#endifFile config.c:
#include "config.h"
/* Definisi: variable hidup di file ini */
int versi_major = 1;
int versi_minor = 5;
const char *nama_aplikasi = "Aplikasi Demo";File main.c:
#include <stdio.h>
#include "config.h"
void tampilkan_versi(void) {
/* Mengakses variabel yang didefinisikan di config.c */
printf("%s v%d.%d\n", nama_aplikasi, versi_major, versi_minor);
}
int main(void) {
tampilkan_versi();
/* Bisa juga dimodifikasi dari main.c */
versi_minor = 6;
printf("Setelah update: v%d.%d\n", versi_major, versi_minor);
return 0;
}Kompilasi dan jalankan:
gcc -std=c11 -Wall -Wextra -I. -o app main.c config.c && ./appOutput yang Dihasilkan:
Aplikasi Demo v1.5
Setelah update: v1.6Perhatikan bahwa main.c dapat membaca dan menulis versi_minor meskipun variabel tersebut didefinisikan di config.c. Setelah baris versi_minor = 6; dijalankan, nilai variabel berubah dan terbaca sebagai 6 pada baris printf berikutnya. Hal ini dimungkinkan karena versi_minor memiliki external linkage.
Mari kita telaah baris-baris kunci di atas:
- Pada
config.hbaris 6-8 (extern int versi_major; ...): deklarasi tanpa inisialisasi. Keywordexternmemberitahu compiler bahwa variabel ini didefinisikan di file lain—compiler tidak mengalokasikan memori di sini. Deklarasi hanya memberi tahu compiler tipe variabel agar pemakaian di kode dapat di-type-check. - Pada
config.cbaris 4-6 (int versi_major = 1; ...): definisi aktual. Di sinilah variabel mendapatkan alamat memori. Inisialisasi (= 1,= 5,= "Aplikasi Demo") bersifat opsional tetapi sangat dianjurkan untuk menghindari uninitialized global. Tanpa inisialisasi, variabel global otomatis bernilai0(untuk tipe skalar) atauNULL(untuk pointer). - Pada
main.cbaris 4 (#include "config.h"): header menyisipkan tiga deklarasiextern. Compiler sekarang tahu tipe ketiga variabel dan mengizinkanmain.cmereferensikannya. - Pada
main.cbaris 7 (printf("%s v%d.%d\n", nama_aplikasi, versi_major, versi_minor);): compiler menghasilkan instruksi mesin yang mereferensikan alamat simbolnama_aplikasi,versi_major, danversi_minor. Pada tahap kompilasi, alamat ini masih kosong (undefined); linker akan mengisinya nanti. - Pada
main.cbaris 12 (versi_minor = 6;): penugasan ke variabel external. Karenaversi_minoradalah variabel global biasa (bukanconst), nilainya dapat diubah dari file mana pun yang melihat deklarasiextern. - Tahap linking: linker melihat
main.omereferensikan simbolversi_minoryang belum terdefinisi. Linker mencari diconfig.odan menemukan definisiint versi_minor = 5;. Simbol dicocokkan dan alamatnya diisi ke instruksi mesin dimain.o. Hasil akhir: executable yang siap dijalankan.
Bahaya Global Mutable State #
External linkage sangat berguna tetapi juga berbahaya jika disalahgunakan. Variabel global yang dapat dimodifikasi dari mana saja (mutable global state) sulit dilacak di proyek besar—bug yang muncul bisa tidak terduga karena banyak fungsi berbagi state yang sama. Untuk meminimalkan risiko, beberapa praktik disarankan:
- Pakai variabel global hanya untuk konstanta (mis.
const char *nama_aplikasi = "Demo";). - Untuk state yang dapat diubah, enkapsulasi via fungsi setter/getter (mis.
int get_versi_minor(void); void set_versi_minor(int v);) dan simpan variabel aktual sebagaistaticdi file.c. - Hindari meng-expose tipe kompleks (
structbesar) sebagai global; gunakan pointer ke handle yang dialokasikan via fungsi factory.