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.

c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -o abi_struct_layout abi_struct_layout.c
./abi_struct_layout

Output yang Dihasilkan:

text
sizeof(PaketA) = 12
  offsetof(a) = 0
  offsetof(b) = 4
  offsetof(c) = 8
sizeof(PaketB) = 8
  offsetof(b) = 0
  offsetof(a) = 4
  offsetof(c) = 5

Mari kita telaah baris-baris kode di atas:

  1. Baris 6-13 (typedef struct PaketA): Struct dengan field char a, int b, char c. Field a menempati byte 0. Field b butuh alignment 4, jadi compiler menyisipkan 3 byte padding di byte 1-3, lalu b menempati byte 4-7. Field c di byte 8. Total padding ekor 3 byte (byte 9-11) supaya array PaketA[2] tetap punya alignment 4 untuk elemen kedua. Hasil: sizeof = 12.
  2. Baris 15-21 (typedef struct PaketB): Field diatur ulang: int b di byte 0-3, char a di byte 4, char c di byte 5. Padding ekor 2 byte (byte 6-7). Hasil: sizeof = 8. Penghematan 33% hanya dengan menukar urutan field.
  3. Baris 26 (offsetof(PaketA, b)): Macro offsetof dari <stddef.h> mengembalikan offset byte field dalam struct. Hasil 4 membuktikan ada 3 byte padding antara a dan b.
  4. Baris 31 (sizeof(PaketB)): Ukuran 8 byte—lebih kecil dari PaketA meski 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.

c
/* 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 */
c
/* 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;
}
c
/* 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:

bash
gcc -std=c11 -Wall -Wextra -o abi_main abi_main.c abi_mathutil.c
./abi_main

Output yang Dihasilkan:

text
7 + 35 = 42
5! = 120

Mari kita telaah baris-baris kode di atas:

  1. Baris 4-6 (abi_mathutil.h include guard #ifndef MATHUTIL_H): Mencegah include berulang. Bila dua file .c sama-sama meng-include abi_mathutil.h (langsung maupun transitif), include guard memastikan deklarasi hanya dibaca sekali per translation unit. Tanpa guard, compiler error “redefinition of struct/function”.
  2. Baris 8-12 (#ifdef __cplusplus extern "C"): Block extern "C" hanya aktif saat header di-include oleh compiler C++ (bukan C). Block ini menonaktifkan name mangling C++ untuk fungsi di dalamnya, sehingga symbol tetap mathutil_tambah (bukan _Z16mathutil_tambahii dsb.). Inilah jembatan ABI antara C dan C++—akan dibahas penuh di chapter C dan C++.
  3. Baris 4-6 (abi_mathutil.c meng-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.
  4. Baris 5 (abi_main.c meng-include abi_mathutil.h): Memberi tahu compiler bahwa fungsi mathutil_tambah dan mathutil_faktorial ada di tempat lain. Compiler memancangkan call ke symbol tersebut; linker kemudian menyatukan dengan definisi di abi_mathutil.o.
  5. Command gcc ... abi_main.c abi_mathutil.c: Compiler menerjemahkan kedua file .c menjadi object file .o, lalu linker menyatukan keduanya. Bila kita hanya menulis gcc abi_main.c (tanpa abi_mathutil.c), linker error undefined 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.

bash
nm abi_mathutil.o

Output yang Dihasilkan:

text
0000000000000014 T mathutil_faktorial
0000000000000000 T mathutil_tambah

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