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:

cpp
// 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:

text
kuadrat_c(9)   = 81
kuadrat(7)     = 49
kuadrat(1.5)   = 2.25
memcpy+strlen  = interoperabilitas (panjang 17)
strcmp("abc","abd") = -1

Bedah program di atas:

  1. Baris 6-8 (extern "C" int kuadrat_c) — fungsi ini akan menghasilkan simbol bernama persis kuadrat_c, tanpa tambahan apa pun. Di dalam extern "C" kita tidak boleh meng-overload: dua fungsi C bernama sama akan bertabrakan saat link, persis seperti di bahasa C.
  2. Baris 11-12 (kuadrat dua versi) — dua fungsi C++ biasa; compiler memberi keduanya simbol berbeda sehingga overloading jadi mungkin.
  3. 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. memcpy menyalin byte, strlen menghitung panjang hingga '\0', dan strcmp membandingkan dua string C.
  4. 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:

bash
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:

text
T _Z7kuadratd
T _Z7kuadrati
T kuadrat_c
T main

Buktinya 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.

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:

c matematika-c.c
/* 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;
}
cpp utama-campur.cpp
// 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++:

bash
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-campur

Output yang Dihasilkan:

text
tambah_c(19, 23) = 42
faktorial_c(6)   = 720

Perhatikan 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++:

c pola header campuran (matematika-c.h)
#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 .