C ABI (Application Binary Interface) adalah kontrak tingkat rendah yang mendefinisikan bagaimana data direpresentasikan dalam memori dan bagaimana fungsi dipanggil pada level mesin. Berbeda dengan API (Application Programming Interface) yang berhubungan dengan deklarasi di source code, ABI berhubungan dengan layout byte, calling convention, dan name mangling pada level object file. C ABI menjadi standar de facto untuk FFI (Foreign Function Interface): hampir semua bahasa modern (Python, Rust, Go, Swift, Zig) dapat memanggil library C karena ABI C sederhana, stabil, dan didokumentasikan dengan baik. Pada chapter ini kita akan memahami dua pilar ABI C: struct layout dengan alignment, dan linkage antar object file lewat header.
Layout Struct dan Alignment #
Compiler mengikuti aturan alignment untuk menempatkan setiap field struct di alamat yang efisien bagi CPU. Field dengan tipe int (4 byte) biasanya ditempatkan pada alamat kelipatan 4. Jika field sebelumnya tidak mengisi penuh, compiler menyisipkan padding byte. Konsekuensinya, dua struct dengan field yang sama tetapi urutan berbeda dapat memiliki ukuran berbeda.
/* File: abi_struct_layout.c - perbedaan ukuran akibat urutan field */
#include <stdio.h>
#include <stddef.h>
typedef struct {
char a; /* 1 byte */
/* 3 byte padding */
int b; /* 4 byte */
char c; /* 1 byte */
/* 3 byte padding */
} PaketA;
typedef struct {
int b; /* 4 byte */
char a; /* 1 byte */
char c; /* 1 byte */
/* 2 byte padding */
} PaketB;
int main(void) {
printf("sizeof(PaketA) = %zu\n", sizeof(PaketA));
printf(" offsetof(a) = %zu\n", offsetof(PaketA, a));
printf(" offsetof(b) = %zu\n", offsetof(PaketA, b));
printf(" offsetof(c) = %zu\n", offsetof(PaketA, c));
printf("sizeof(PaketB) = %zu\n", sizeof(PaketB));
printf(" offsetof(b) = %zu\n", offsetof(PaketB, b));
printf(" offsetof(a) = %zu\n", offsetof(PaketB, a));
printf(" offsetof(c) = %zu\n", offsetof(PaketB, c));
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o abi_struct_layout abi_struct_layout.c
./abi_struct_layoutOutput yang Dihasilkan:
sizeof(PaketA) = 12
offsetof(a) = 0
offsetof(b) = 4
offsetof(c) = 8
sizeof(PaketB) = 8
offsetof(b) = 0
offsetof(a) = 4
offsetof(c) = 5Mari kita telaah baris-baris kode di atas:
- Baris 6-13 (
typedef struct PaketA): Struct dengan fieldchar a,int b,char c. Fieldamenempati byte 0. Fieldbbutuh alignment 4, jadi compiler menyisipkan 3 byte padding di byte 1-3, lalubmenempati byte 4-7. Fieldcdi byte 8. Total padding ekor 3 byte (byte 9-11) supaya arrayPaketA[2]tetap punya alignment 4 untuk elemen kedua. Hasil:sizeof = 12. - Baris 15-21 (
typedef struct PaketB): Field diatur ulang:int bdi byte 0-3,char adi byte 4,char cdi byte 5. Padding ekor 2 byte (byte 6-7). Hasil:sizeof = 8. Penghematan 33% hanya dengan menukar urutan field. - Baris 26 (
offsetof(PaketA, b)): Macrooffsetofdari<stddef.h>mengembalikan offset byte field dalam struct. Hasil4membuktikan ada 3 byte padding antaraadanb. - Baris 31 (
sizeof(PaketB)): Ukuran 8 byte—lebih kecil dariPaketAmeski field sama persis. Pelajaran: saat mendesain struct yang dipakai di array besar atau dilewatkan lintas FFI, urutkan field dari tipe terbesar ke terkecil.
Linkage Antar Object File #
C ABI juga mencakup external linkage: bagaimana symbol (nama fungsi/global variable) di-resolve oleh linker saat menyatukan banyak file .o. Deklarasi fungsi di header file memberi tahu compiler signature fungsi, dan definisi di file .c lain menyediakan implementasinya. Linker kemudian menyatukan keduanya. Inilah mekanisme yang dipakai setiap FFI: sebuah library (.so atau .dll) mengekspor symbol, dan konsumen (file C lain, atau Python/Rust/Go) tinggal mendeklarasikan prototype lalu memanggil.
/* File: abi_mathutil.h - deklarasi (header) */
#ifndef MATHUTIL_H
#define MATHUTIL_H
#ifdef __cplusplus
extern "C" {
#endif
int mathutil_tambah(int a, int b);
int mathutil_faktorial(int n);
#ifdef __cplusplus
}
#endif
#endif /* MATHUTIL_H *//* File: abi_mathutil.c - definisi (implementasi) */
#include "abi_mathutil.h"
int mathutil_tambah(int a, int b) {
return a + b;
}
int mathutil_faktorial(int n) {
int hasil = 1;
for (int i = 2; i <= n; i++) {
hasil *= i;
}
return hasil;
}/* File: abi_main.c - konsumen: panggil fungsi dari file lain */
#include <stdio.h>
#include "abi_mathutil.h"
int main(void) {
int x = 7, y = 35;
int sum = mathutil_tambah(x, y);
int f5 = mathutil_faktorial(5);
printf("%d + %d = %d\n", x, y, sum);
printf("5! = %d\n", f5);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o abi_main abi_main.c abi_mathutil.c
./abi_mainOutput yang Dihasilkan:
7 + 35 = 42
5! = 120Mari kita telaah baris-baris kode di atas:
- Baris 4-6 (
abi_mathutil.hinclude guard#ifndef MATHUTIL_H): Mencegah include berulang. Bila dua file.csama-sama meng-includeabi_mathutil.h(langsung maupun transitif), include guard memastikan deklarasi hanya dibaca sekali per translation unit. Tanpa guard, compiler error “redefinition of struct/function”. - Baris 8-12 (
#ifdef __cplusplus extern "C"): Blockextern "C"hanya aktif saat header di-include oleh compiler C++ (bukan C). Block ini menonaktifkan name mangling C++ untuk fungsi di dalamnya, sehingga symbol tetapmathutil_tambah(bukan_Z16mathutil_tambahiidsb.). Inilah jembatan ABI antara C dan C++—akan dibahas penuh di chapter C dan C++. - Baris 4-6 (
abi_mathutil.cmeng-include header sendiri): Implementasi meng-include header-nya sendiri agar compiler memverifikasi bahwa signature definisi cocok dengan deklarasi header. Bila ada ketidakcocokan (misalnya return type berbeda), compiler memunculkan error. - Baris 5 (
abi_main.cmeng-includeabi_mathutil.h): Memberi tahu compiler bahwa fungsimathutil_tambahdanmathutil_faktorialada di tempat lain. Compiler memancangkan call ke symbol tersebut; linker kemudian menyatukan dengan definisi diabi_mathutil.o. - Command
gcc ... abi_main.c abi_mathutil.c: Compiler menerjemahkan kedua file.cmenjadi object file.o, lalu linker menyatukan keduanya. Bila kita hanya menulisgcc abi_main.c(tanpaabi_mathutil.c), linker errorundefined reference to 'mathutil_tambah'.
Verifikasi Symbol dengan nm #
Setelah kompilasi, kita dapat memeriksa symbol apa saja yang diekspor sebuah object file. Tool nm dari GNU binutils menampilkan tabel symbol.
nm abi_mathutil.oOutput yang Dihasilkan:
0000000000000014 T mathutil_faktorial
0000000000000000 T mathutil_tambahKolom T berarti simbol berada di section .text (kode) dan bersifat global (external linkage). Inilah simbol yang dapat dipanggil dari object file lain. Bila kita menandai fungsi dengan static, kolom akan berubah menjadi t (lowercase) yang berarti local—linker tidak akan menyatukan dengan pemanggil di file lain.