Section ini membuka babak baru: selama perjalanan course ini program kita berdiri sendiri, sedangkan dunia nyata penuh kode yang sudah jadi — API sistem operasi, driver, database, library visi komputer — dan hampir semuanya berbicara dalam satu bahasa pengantar yang sama: C. C++ dirancang sejak awal untuk kompatibel dengan C, dan itulah alasan C++ menjadi fondasi banyak ekosistem. Pada Chapter ini kita pelajari fondasi yang paling sering dipakai: memanggil fungsi C dari program C++, memahami apa yang dilakukan extern "C" terhadap nama simbol, dan menyusun proyek yang mencampur file .c dan .cpp dalam satu build. Semua contohnya kita uji sungguhan dengan gcc dan g++ yang ada di lingkungan course ini.
Mengapa C adalah Titik Temu Universal #
Sistem operasi mengekspos layarnya lewat API berbahasa C: open, read, socket di Linux, dan WinAPI di Windows. Hampir setiap ekosistem bahasa — Python, Rust, Java, C# — menyediakan jalur untuk memanggil library C, karena C ABI (Application Binary Interface, kontrak pemanggilan di level biner) adalah bahasa bersama yang paling lama stabil. Bagi C++, keuntungannya istimewa: kita tidak butuh lapisan FFI (Foreign Function Interface) apa pun. Cukup deklarasi yang tepat, dan fungsi C bisa dipanggil seperti fungsi C++ biasa. Library sebesar SQLite, SDL, atau OpenGL punya antarmuka C, dan itulah pintu masuk resminya bagi semua bahasa — termasuk C++.
extern “C”: Mematikan Name Mangling #
Deklarasi extern "C" memberi tahu compiler: “fungsi ini mengikuti aturan penamaan C, bukan C++”. Aturan penamaan C++ itu sendiri disebut name mangling: karena C++ mengizinkan dua fungsi bernama sama dengan tipe parameter berbeda (overloading), compiler mengubah nama fungsi menjadi simbol yang mengodekan namespace dan tipe parameternya. Bahasa C tidak punya overloading, sehingga namanya dibiarkan apa adanya. Perhatikan contoh berikut — satu file berisi fungsi ber-linkage C, fungsi C++ yang di-overload, dan pemakaian library C standar:
// demo-extern-c.cpp — extern "C" dan pemakaian library C dari C++
#include <cstring> // library C: memcpy, strlen, strcmp (versi std::)
#include <cstdio>
// Fungsi dengan linkage C: nama simbolnya tidak dimangled
extern "C" int kuadrat_c(int x) {
return x * x;
}
// Fungsi C++ biasa — BISA di-overload karena namanya dimangled
int kuadrat(int x) { return x * x; }
double kuadrat(double x) { return x * x; }
int main() {
// 1. Panggil fungsi ber-linkage C
std::printf("kuadrat_c(9) = %d\n", kuadrat_c(9));
// 2. Overload C++: compiler memilih lewat tipe parameter
std::printf("kuadrat(7) = %d\n", kuadrat(7));
std::printf("kuadrat(1.5) = %.2f\n", kuadrat(1.5));
// 3. Library C standar lewat <cstring>: fungsinya tetap milik C
const char* sumber = "interoperabilitas";
char tujuan[32] = {};
std::memcpy(tujuan, sumber, std::strlen(sumber) + 1);
std::printf("memcpy+strlen = %s (panjang %zu)\n", tujuan, std::strlen(tujuan));
std::printf("strcmp(\"abc\",\"abd\") = %d\n", std::strcmp("abc", "abd"));
return 0;
}Output yang Dihasilkan:
kuadrat_c(9) = 81
kuadrat(7) = 49
kuadrat(1.5) = 2.25
memcpy+strlen = interoperabilitas (panjang 17)
strcmp("abc","abd") = -1Bedah program di atas:
- Baris 6-8 (
extern "C" int kuadrat_c) — fungsi ini akan menghasilkan simbol bernama persiskuadrat_c, tanpa tambahan apa pun. Di dalamextern "C"kita tidak boleh meng-overload: dua fungsi C bernama sama akan bertabrakan saat link, persis seperti di bahasa C. - Baris 11-12 (
kuadratdua versi) — dua fungsi C++ biasa; compiler memberi keduanya simbol berbeda sehingga overloading jadi mungkin. - Baris 20-22 (
std::memcpy,std::strlen) — header<cstring>adalah bungkus C++ untuk header C<string.h>: fungsinya milik library C, dipanggil dari C++ tanpa lapisan apa pun.memcpymenyalin byte,strlenmenghitung panjang hingga'\0', danstrcmpmembandingkan dua string C. - Baris 21 (
char tujuan[32] = {}) — semua elemen di-nol-kan, menjamin ada'\0'penutup setelah salinan. Lupa buffer penutup adalah bug klasik saat memakai fungsi C.
Melihat Simbol yang Sebenarnya dengan nm #
Klaim tentang nama simbol tadi bisa kita buktikan. Kompilasi file di atas menjadi object file, lalu periksa simbolnya dengan tool nm — tanda T berarti simbol terdefinisi (defined) di bagian teks (kode). Kolom alamat kita buang dengan awk agar outputnya ringkas:
g++ -std=c++23 -Wall -Wextra -c demo-extern-c.cpp -o demo-extern-c.o
nm demo-extern-c.o | grep ' T ' | awk '{print $2, $3}'Output yang Dihasilkan:
T _Z7kuadratd
T _Z7kuadrati
T kuadrat_c
T mainBuktinya terlihat jelas: dua fungsi C++ bernama kuadrat menjadi simbol _Z7kuadratd (versi double) dan _Z7kuadrati (versi int) — huruf d dan i di akhir adalah tipe parameternya. Sementara kuadrat_c dan main tetap memakai nama polos karena keduanya tidak di-mangle (main adalah pengecualian yang diatur standar). Inilah sebabnya deklarasi extern "C" wajib ada saat memanggil C: tanpa itu, compiler mencari simbol _Z9tambah_cii padahal library C hanya punya tambah_c, dan linker melapor undefined reference.
Kompilasi Campuran: gcc untuk C, g++ untuk Link #
Sekarang wujudkan skenario yang paling sering ditemui di proyek nyata: file .c dari pihak lain, program C++ milik kita. Kita buat modul C kecil dan program C++ yang memanggilnya:
/* matematika-c.c — kode C murni, dikompilasi dengan gcc */
int tambah_c(int a, int b) {
return a + b;
}
int faktorial_c(int n) {
int hasil = 1;
for (int i = 2; i <= n; ++i) {
hasil *= i;
}
return hasil;
}// utama-campur.cpp — C++ yang memanggil fungsi hasil kompilasi C
#include <cstdio>
// Deklarasi dengan linkage C: nama tidak dimangled, cocok dengan
// simbol di matematika-c.o hasil kompilasi gcc
extern "C" {
int tambah_c(int a, int b);
int faktorial_c(int n);
}
int main() {
std::printf("tambah_c(19, 23) = %d\n", tambah_c(19, 23));
std::printf("faktorial_c(6) = %d\n", faktorial_c(6));
return 0;
}Perintah build membagi tugas dengan benar: gcc mengompilasi kode C menjadi matematika-c.o, lalu g++ mengompilasi utama-campur.cpp dan me-link keduanya. Kita selalu menyelesaikan link dengan g++ karena linker butuh library runtime C++ (libstdc++) yang otomatis ditambahkan oleh driver g++:
gcc -std=c17 -Wall -Wextra -c matematika-c.c -o matematika-c.o
g++ -std=c++23 -Wall -Wextra utama-campur.cpp matematika-c.o -o utama-campur
./utama-campurOutput yang Dihasilkan:
tambah_c(19, 23) = 42
faktorial_c(6) = 720Perhatikan bahwa utama-campur.cpp mendeklarasikan tambah_c dan faktorial_c di dalam blok extern "C" — itulah yang membuat simbol yang dicari linker cocok dengan isi matematika-c.o. Hapus deklarasi itu dan kompilasi akan gagal dengan undefined reference, bukan karena kodenya salah, melainkan karena nama simbolnya tidak bertemu.
Header yang Cocok untuk Dua Bahasa #
Kalau modul C itu milik kita sendiri, ada pola baku agar satu header bisa dipakai compiler C maupun C++:
#ifndef MATEMATIKA_C_H
#define MATEMATIKA_C_H
#ifdef __cplusplus
extern "C" {
#endif
int tambah_c(int a, int b);
int faktorial_c(int n);
#ifdef __cplusplus
}
#endif
#endif /* MATEMATIKA_C_H */Makro __cplusplus hanya terdefinisi bila file diolah compiler C++ — jadi bagian extern "C" hanya terlihat oleh C++, dan compiler C mengabaikannya dengan rapi. Dengan pola ini, pemakai C++ cukup #include "matematika-c.h" tanpa menulis extern "C" sendiri.
Fondasinya sudah di tangan: C++ bisa memanggil C, dan kita tahu persis apa yang terjadi di level simbol. Chapter berikutnya membedah aturan main yang lebih dalam lagi di balik pertemuan dua dunia ini — perbedaan ABI antara C dan C++, dan kapan perbedaan itu menjadi masalah nyata.
Referensi: Language linkage di cppreference , header <cstring> di cppreference .