C dan C++ adalah dua bahasa yang berkerabat dekat—C++ dibangun di atas sintaks C dan mengompilasi sebagian besar kode C tanpa modifikasi. Namun, C++ menambahkan banyak fitur (class, template, overload, namespace) yang mengharuskan compiler C++ melakukan name mangling: mengubah nama symbol agar dapat merepresentasikan informasi tipe yang tidak ada di C. Akibatnya, fungsi int foo(int) di kompilasi C++ menjadi symbol seperti _Z3fooi, sedangkan di C tetap foo. Untuk menjembatani kedua dunia, C++ menyediakan keyword extern "C" yang mematikan name mangling pada fungsi tertentu. Pada chapter ini kita akan membangun library C yang dipanggil C++, dan library C++ yang dipanggil C.

Name Mangling di C++ #

Name mangling adalah proses compiler mengkodekan informasi tipe ke nama symbol. Tujuannya: mendukung function overloading (beberapa fungsi dengan nama sama tetapi parameter berbeda) dan namespace. Tanpa mangling, linker tidak dapat membedakan foo(int) dari foo(double).

bash
# Bandingkan symbol C vs C++ untuk fungsi yang sama
cat > foo_c.c << 'EOF'
int cpp_foo(int x) { return x * 2; }
EOF
cat > foo_cpp.cxx << 'EOF'
int cpp_foo(int x) { return x * 2; }
EOF
gcc -c foo_c.c -o foo_c.o
g++ -c foo_cpp.cxx -o foo_cpp.o
nm foo_c.o | grep foo
nm foo_cpp.o | grep foo

Output yang Dihasilkan:

text
0000000000000000 T cpp_foo        # dari gcc (C murni)
0000000000000000 T _Z7cpp_fooi    # dari g++ (C++ mangled)

Symbol _Z7cpp_fooi dapat didekode: _Z (prefix C++), 7cpp_foo (nama 7 karakter), i (parameter int). Symbol C murni cpp_foo tidak punya dekorasi. Inilah mengapa library C++ tidak dapat dipanggil langsung dari C tanpa extern "C"—symbol yang dicari linker berbeda.

Header Bersama: extern "C" untuk C++ #

Solusi standar adalah menulis header yang dapat di-include oleh compiler C maupun C++. Header menyertakan block extern "C" yang hanya aktif di C++ (dideteksi lewat macro __cplusplus), sehingga compiler C mengabaikannya dan compiler C++ mematikan name mangling untuk fungsi di dalamnya.

c
/* File: mesin.h - header bersama untuk C dan C++ */
#ifndef MESIN_H
#define MESIN_H

#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

int    mesin_kuadrat(int x);
double mesin_akar(double x);
const char *mesin_label(void);

/* Opaque pointer yang dapat dipakai C maupun C++ */
typedef struct Mesin Mesin;
Mesin *mesin_buat(int inisial);
void   mesin_hancurkan(Mesin *m);
int    mesin_nilai(const Mesin *m);

#ifdef __cplusplus
}  /* extern "C" */
#endif

#endif /* MESIN_H */

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-9 (#ifdef __cplusplus extern "C" {): Macro __cplusplus didefinisikan otomatis oleh compiler C++ (gcc mendefinisikan __cplusplus dengan nilai versi standar). Compiler C tidak mendefinisikannya, jadi block extern "C" dilewati. Pola ini menjadikan header portabel untuk kedua compiler.
  2. Baris 11-13 (deklarasi fungsi): Fungsi-fungsi ini berada di dalam block extern "C". Saat header di-include C++, compiler tahu symbol harus tidak di-mangle. Saat di-include C, compiler C mengabaikan block dan memperlakukan deklarasi seperti biasa.
  3. Baris 16-19 (typedef struct Mesin Mesin;): Opaque pointer. Karena struct Mesin tidak punya body, layout tidak relevan. Pointer dapat dilewatkan antara C dan C++ tanpa khawatir perbedaan ABI struct.
  4. Baris 21 (} /* extern "C" */): Menutup block. Komentar menjelaskan penutup untuk mempermudah pembaca menelusuri pasangan { dan }.

C++ Memanggil Library C #

Implementasi library ditulis dalam C murni (mesin.c), dikompilasi dengan gcc. Konsumennya adalah program C++ (cpp_panggil.cxx) yang di-include mesin.h dan memanggil fungsi sebagai fungsi biasa.

c
/* File: mesin.c - implementasi dalam C murni */
#include "mesin.h"
#include <math.h>
#include <stdlib.h>

int mesin_kuadrat(int x) {
    return x * x;
}

double mesin_akar(double x) {
    return sqrt(x);
}

const char *mesin_label(void) {
    return "mesin-c-v1";
}

struct Mesin {
    int nilai;
};

Mesin *mesin_buat(int inisial) {
    Mesin *m = (Mesin *) malloc(sizeof(Mesin));
    if (!m) return NULL;
    m->nilai = inisial;
    return m;
}

void mesin_hancurkan(Mesin *m) {
    if (!m) return;
    free(m);
}

int mesin_nilai(const Mesin *m) {
    return m ? m->nilai : 0;
}
cpp
// File: cpp_panggil.cxx - C++ memanggil library C lewat extern "C"
#include <iostream>
#include "mesin.h"

int main() {
    int k = mesin_kuadrat(7);
    std::cout << "7^2 = " << k << std::endl;

    double a = mesin_akar(50.0);
    std::cout << "sqrt(50) = " << a << std::endl;

    std::cout << "Label: " << mesin_label() << std::endl;

    Mesin *m = mesin_buat(42);
    if (m) {
        std::cout << "Mesin nilai: " << mesin_nilai(m) << std::endl;
        mesin_hancurkan(m);
    }
    return 0;
}

Compile dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -c mesin.c -o mesin_c.o
g++ -std=c++17 -Wall -Wextra -o cpp_panggil cpp_panggil.cxx mesin_c.o -lm
./cpp_panggil

Output yang Dihasilkan:

text
7^2 = 49
sqrt(50) = 7.07107
Label: mesin-c-v1
Mesin nilai: 42

Mari kita telaah baris-baris kode di atas:

  1. Compile terpisah (gcc untuk mesin.c, g++ untuk cpp_panggil.cxx): Library dikompilasi dengan gcc (C), konsumen dengan g++ (C++). Keduanya menghasilkan object file .o dengan format ELF sama, sehingga linker dapat menyatukan. Inilah inti interoperabilitas: binary format compatible, hanya name mangling yang berbeda.
  2. Baris 3 (#include "mesin.h"): Header bersama. Saat g++ meng-include header, macro __cplusplus aktif, sehingga block extern "C" diaktifkan. Compiler C++ tahu mesin_kuadrat harus dipanggil dengan C ABI (no mangling).
  3. Baris 6 (mesin_kuadrat(7)): Pemanggilan terlihat seperti fungsi biasa di C++. Compiler C++ menghasilkan call ke symbol mesin_kuadrat (tidak di-mangle) sehingga linker menemukan definisi di mesin_c.o.
  4. Flag -lm: Library mesin.c memakai sqrt dari libm. Karena link dilakukan oleh g++, library libm harus dinyatakan eksplisit. (glibc modern sering menarik libm otomatis, tetapi eksplisit lebih aman.)
  5. Verifikasi symbol: Setelah kompilasi, nm mesin_c.o | grep mesin menunjukkan mesin_akar, mesin_buat, mesin_kuadrat, dst. semua tanpa mangling. Inilah mengapa linker dapat menyatukan dengan call dari C++.

C Memanggil Fungsi yang Dikompilasi dari C++ #

Arah sebaliknya juga mungkin: kita menulis kode C++ tetapi ingin fungsi tertentu dapat dipanggil dari C. Solusinya: tambahkan extern "C" pada definisi fungsi di file .cxx. C++ compiler tetap mengompilasi body (boleh memakai fitur C++ seperti std::cout), tetapi symbol yang dihasilkan tidak di-mangle.

cpp
// File: cpp_lib.cxx - C++ function diekspos dengan extern "C"
#include "mesin.h"
#include <iostream>

/* Fungsi C++ yang dipublikasikan dengan C ABI */
extern "C" int mesin_dobel(int x) {
    std::cout << "[C++] memproses " << x << std::endl;
    return x * 2;
}

/* Fungsi C++ biasa (tidak diekspos) - akan ter-mangle */
extern "C++" int mesin_internal_cpp(int x) {
    return x + 1;
}
c
/* File: c_panggil.c - C memanggil fungsi yang dikompilasi dari C++ */
#include <stdio.h>
#include "mesin.h"

/* Deklarasi fungsi yang implementasinya di file C++ */
extern int mesin_dobel(int x);

int main(void) {
    int hasil = mesin_dobel(21);
    printf("mesin_dobel(21) = %d\n", hasil);
    printf("mesin_label() = %s\n", mesin_label());
    return 0;
}

Compile dan jalankan:

bash
g++ -std=c++17 -Wall -Wextra -c cpp_lib.cxx -o cpp_lib.o
gcc -std=c11 -Wall -Wextra -c mesin.c -o mesin_c.o
gcc -std=c11 -Wall -Wextra -o c_panggil c_panggil.c mesin_c.o cpp_lib.o -lstdc++ -lm
./c_panggil

Output yang Dihasilkan:

text
[C++] memproses 21
mesin_dobel(21) = 42
mesin_label() = mesin-c-v1

Verifikasi symbol di cpp_lib.o:

bash
nm cpp_lib.o | grep mesin
text
000000000000004d T _Z18mesin_internal_cppi
0000000000000000 T mesin_dobel

Mari kita telaah baris-baris kode di atas:

  1. Baris 6 (extern "C" int mesin_dobel(int x)): Fungsi C++ yang di-mark extern "C". Body boleh memakai fitur C++ (std::cout, class, dll), tetapi symbol yang dihasilkan mesin_dobel (tanpa mangling). Inilah yang memungkinkan C memanggil fungsi ini.
  2. Baris 11 (extern "C++" int mesin_internal_cpp(int x)): Fungsi yang secara eksplisit ditandai extern "C++". Symbol mangled menjadi _Z18mesin_internal_cppi. Fungsi ini hanya dapat dipanggil dari C++; tidak dapat diakses dari C karena nama symbol tidak diketahui tanpa dekoder mangling.
  3. Baris 7 di c_panggil.c (extern int mesin_dobel(int x);): Deklarasi fungsi di sisi C. Tanpa extern pun sebenarnya valid (default linkage C eksternal), tetapi extern membuat intent eksplisit. C tidak perlu tahu implementasi ada di C++—hanya perlu deklarasi signature.
  4. Flag -lstdc++: Karena cpp_lib.cxx memakai std::cout, runtime C++ perlu di-link. gcc tidak otomatis menarik libstdc++; kita nyatakan eksplisit. Bila tidak ada -lstdc++, error linker undefined reference to std::cout.
  5. Symbol _Z18mesin_internal_cppi: Symbol yang ter-mangle tidak dapat diakses dari C murni—C compiler tidak tahu cara memanggilnya. Tetap terlihat di nm sebagai informasi, tetapi tidak berguna untuk C. Hanya mesin_dobel yang dapat di-call dari C.