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).
# 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 fooOutput yang Dihasilkan:
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.
/* 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:
- Baris 7-9 (
#ifdef __cplusplus extern "C" {): Macro__cplusplusdidefinisikan otomatis oleh compiler C++ (gcc mendefinisikan__cplusplusdengan nilai versi standar). Compiler C tidak mendefinisikannya, jadi blockextern "C"dilewati. Pola ini menjadikan header portabel untuk kedua compiler. - 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. - Baris 16-19 (
typedef struct Mesin Mesin;): Opaque pointer. Karenastruct Mesintidak punya body, layout tidak relevan. Pointer dapat dilewatkan antara C dan C++ tanpa khawatir perbedaan ABI struct. - 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.
/* 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;
}// 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:
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_panggilOutput yang Dihasilkan:
7^2 = 49
sqrt(50) = 7.07107
Label: mesin-c-v1
Mesin nilai: 42Mari kita telaah baris-baris kode di atas:
- Compile terpisah (
gccuntukmesin.c,g++untukcpp_panggil.cxx): Library dikompilasi dengangcc(C), konsumen dengang++(C++). Keduanya menghasilkan object file.odengan format ELF sama, sehingga linker dapat menyatukan. Inilah inti interoperabilitas: binary format compatible, hanya name mangling yang berbeda. - Baris 3 (
#include "mesin.h"): Header bersama. Saatg++meng-include header, macro__cplusplusaktif, sehingga blockextern "C"diaktifkan. Compiler C++ tahumesin_kuadratharus dipanggil dengan C ABI (no mangling). - Baris 6 (
mesin_kuadrat(7)): Pemanggilan terlihat seperti fungsi biasa di C++. Compiler C++ menghasilkan call ke symbolmesin_kuadrat(tidak di-mangle) sehingga linker menemukan definisi dimesin_c.o. - Flag
-lm: Librarymesin.cmemakaisqrtdarilibm. Karena link dilakukan olehg++, librarylibmharus dinyatakan eksplisit. (glibc modern sering menariklibmotomatis, tetapi eksplisit lebih aman.) - Verifikasi symbol: Setelah kompilasi,
nm mesin_c.o | grep mesinmenunjukkanmesin_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.
// 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;
}/* 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:
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_panggilOutput yang Dihasilkan:
[C++] memproses 21
mesin_dobel(21) = 42
mesin_label() = mesin-c-v1Verifikasi symbol di cpp_lib.o:
nm cpp_lib.o | grep mesin000000000000004d T _Z18mesin_internal_cppi
0000000000000000 T mesin_dobelMari kita telaah baris-baris kode di atas:
- Baris 6 (
extern "C" int mesin_dobel(int x)): Fungsi C++ yang di-markextern "C". Body boleh memakai fitur C++ (std::cout, class, dll), tetapi symbol yang dihasilkanmesin_dobel(tanpa mangling). Inilah yang memungkinkan C memanggil fungsi ini. - Baris 11 (
extern "C++" int mesin_internal_cpp(int x)): Fungsi yang secara eksplisit ditandaiextern "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. - Baris 7 di
c_panggil.c(extern int mesin_dobel(int x);): Deklarasi fungsi di sisi C. Tanpaexternpun sebenarnya valid (default linkage C eksternal), tetapiexternmembuat intent eksplisit. C tidak perlu tahu implementasi ada di C++—hanya perlu deklarasi signature. - Flag
-lstdc++: Karenacpp_lib.cxxmemakaistd::cout, runtime C++ perlu di-link.gcctidak otomatis menariklibstdc++; kita nyatakan eksplisit. Bila tidak ada-lstdc++, error linkerundefined reference to std::cout. - Symbol
_Z18mesin_internal_cppi: Symbol yang ter-mangle tidak dapat diakses dari C murni—C compiler tidak tahu cara memanggilnya. Tetap terlihat dinmsebagai informasi, tetapi tidak berguna untuk C. Hanyamesin_dobelyang dapat di-call dari C.