Audit portabilitas adalah proses memeriksa kode C untuk menemukan asumsi yang hanya valid di satu platform atau compiler. Beberapa jebakan umum: mengasumsikan sizeof(int) == 4 (berbeda di platform 16-bit), menganggap char selalu signed (berbeda di ARM), melakukan signed overflow (undefined behavior), dan memakai int untuk ukuran buffer (overflow di objek >2GB). Bab ini mendemonstrasikan audit portabilitas dengan kode yang memeriksa sizeof berbagai tipe, menunjukkan perbedaan perilaku char signed vs unsigned, dan menerapkan pola aman untuk aritmetika yang dapat overflow. Setelah membaca bab ini, kita akan tahu bagian mana dari kode kita yang perlu diperbaiki agar portabel ke compiler dan arsitektur lain.
Audit sizeof dan Tipe Integer #
Tipe dasar C (int, long, short) tidak memiliki ukuran yang tetap—hanya batas minimum ditentukan oleh standar. Sebagai contoh, int minimal 2 byte (16-bit) tetapi bisa 4 byte (32-bit) atau 8 byte (64-bit) tergantung platform. long di Linux 64-bit adalah 8 byte, tetapi di Windows 64-bit tetap 4 byte (LLP64 model). Audit pertama yang perlu dilakukan adalah mencatat sizeof setiap tipe yang kita pakai:
// File: port_1.c - Audit portabilitas: sizeof, signed overflow, tipe integer
#include <stdio.h>
#include <stdint.h>
#include <limits.h>
int main(void) {
// 1. Asumsi sizeof yang sering salah
printf("=== Audit sizeof ===\n");
printf("sizeof(char) = %zu (selalu 1, guaranteed)\n", sizeof(char));
printf("sizeof(short) = %zu (minimal 2 byte)\n", sizeof(short));
printf("sizeof(int) = %zu (minimal 2, biasanya 4)\n", sizeof(int));
printf("sizeof(long) = %zu (4 di Windows, 8 di Linux 64-bit)\n", sizeof(long));
printf("sizeof(long long)= %zu (minimal 8 byte)\n", sizeof(long long));
printf("sizeof(void*) = %zu (4 atau 8 byte)\n", sizeof(void*));
printf("sizeof(size_t) = %zu (mengikuti pointer)\n", sizeof(size_t));
// 2. Gunakan stdint.h untuk ukuran eksak
printf("\n=== Tipe eksak dari stdint.h ===\n");
printf("sizeof(int32_t) = %zu (selalu 4 byte)\n", sizeof(int32_t));
printf("sizeof(int64_t) = %zu (selalu 8 byte)\n", sizeof(int64_t));
printf("sizeof(uintptr_t) = %zu (cukup besar untuk pointer)\n", sizeof(uintptr_t));
// 3. Batas tipe signed
printf("\n=== Batas tipe signed ===\n");
printf("INT_MAX = %d\n", INT_MAX);
printf("INT_MIN = %d\n", INT_MIN);
printf("INT32_MAX= %d\n", INT32_MAX);
printf("INT64_MAX= %lld\n", (long long)INT64_MAX);
// 4. Demonstrasikan signed overflow (UB!)
printf("\n=== Signed overflow (UB!) ===\n");
int a = INT_MAX;
printf("a = INT_MAX = %d\n", a);
printf("a + 1 (UB!) = %d (tidak terdefinisi, bisa crash/corrupt)\n", a + 1);
// 5. Gunakan unsigned untuk arithmetic yang boleh overflow
unsigned int b = UINT_MAX;
printf("\nb = UINT_MAX = %u\n", b);
printf("b + 1 (well-defined wrap) = %u\n", b + 1);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -pedantic -o port_1 port_1.c
./port_1Output yang Dihasilkan (x86-64 Linux):
=== Audit sizeof ===
sizeof(char) = 1 (selalu 1, guaranteed)
sizeof(short) = 2 (minimal 2 byte)
sizeof(int) = 4 (minimal 2, biasanya 4)
sizeof(long) = 8 (4 di Windows, 8 di Linux 64-bit)
sizeof(long long)= 8 (minimal 8 byte)
sizeof(void*) = 8 (4 atau 8 byte)
sizeof(size_t) = 8 (mengikuti pointer)
=== Tipe eksak dari stdint.h ===
sizeof(int32_t) = 4 (selalu 4 byte)
sizeof(int64_t) = 8 (selalu 8 byte)
sizeof(uintptr_t) = 8 (cukup besar untuk pointer)
=== Batas tipe signed ===
INT_MAX = 2147483647
INT_MIN = -2147483648
INT32_MAX= 2147483647
INT64_MAX= 9223372036854775807
=== Signed overflow (UB!) ===
a = INT_MAX = 2147483647
a + 1 (UB!) = -2147483648 (tidak terdefinisi, bisa crash/corrupt)
b = UINT_MAX = 4294967295
b + 1 (well-defined wrap) = 0Mari kita telaah baris-baris kode di atas:
- Baris 10-17 (
printf("sizeof(char) = %zu\n", sizeof(char));dst.): Auditsizeofsetiap tipe dasar.sizeof(char)selalu1dijamin standar C; tipe lain hanya punya batas minimum.sizeof(long)adalah 8 di Linux 64-bit (LP64 model) tetapi 4 di Windows 64-bit (LLP64 model)—perbedaan ini sering menyebabkan bug saat kode Linux dipindah ke Windows. - Baris 20-23 (
sizeof(int32_t) = 4dst.): Tipe daristdint.hmemiliki ukuran eksak.int32_tselalu 4 byte di platform apapun yang mendukung C99.int64_tselalu 8 byte.uintptr_tcukup besar untuk menyimpan pointer—di x86-32 adalah 4 byte, di x86-64 adalah 8 byte. - Baris 26-30 (
INT_MAX,INT_MIN,INT32_MAX,INT64_MAX): Konstanta batas dari<limits.h>dan<stdint.h>. Selalu pakai konstanta ini alih-alih meng-hardcode angka seperti2147483647. - Baris 33-35 (
int a = INT_MAX; a + 1): Signed overflow adalah undefined behavior di C. Output di atas menunjukkan-2147483648(wrap-around), tetapi compiler berhak mengoptimalkan kode dengan asumsi overflow tidak terjadi—hasil aktual bisa tidak terduga (mis. compiler menghapus cek bounds karena asumsii + 1 > iselalu benar). - Baris 38-40 (
unsigned int b = UINT_MAX; b + 1): Unsigned overflow well-defined—selalu wrap-around modulo2^N. Untuk arithmetic yang boleh wrap (mis. hash, checksum), gunakan tipe unsigned. Untuk arithmetic yang overflow-nya error, gunakan signed dengan explicit overflow check.
Jebakan Char Signed dan Pola size_t #
Tipe char di C bisa signed atau unsigned, tergantung implementasi compiler. GCC di x86 default ke signed char, tetapi ARM compiler sering default ke unsigned char. Kode yang menganggap char selalu signed (mis. membandingkan dengan nilai negatif) akan gagal di platform unsigned. Selain itu, penggunaan int untuk ukuran buffer bisa overflow di objek besar—selalu gunakan size_t:
// File: port_2.c - Audit: char signed/unsigned, NULL check, size_t
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
// Bahaya: char bisa signed atau unsigned tergantung platform
// x86: signed char (-128..127), ARM: unsigned char (0..255)
static int hitung_set_bit(unsigned char c) {
int n = 0;
while (c) {
n += c & 1;
c >>= 1;
}
return n;
}
// Fungsi yang menggunakan size_t untuk parameter ukuran (bukan int)
static size_t hitung_panjang_string(const char *s, size_t max_buf) {
size_t len = 0;
while (len < max_buf && s[len] != '\0') {
++len;
}
return len;
}
// Pola aman: cek overflow sebelum penambahan
static int aman_tambah(int a, int b, int *hasil) {
// Cek overflow sebelum benar-benar menjumlahkan
if ((b > 0 && a > INT_MAX - b) ||
(b < 0 && a < INT_MIN - b)) {
return -1; // overflow
}
*hasil = a + b;
return 0;
}
int main(void) {
// 1. Char signness: kode lama sering salah menganggap char selalu signed
unsigned char c = 200;
unsigned char uc = 200;
printf("unsigned char c=200 -> %d\n", c);
printf("unsigned char uc=200 -> %d\n", uc);
// 2. Set bit pada char
unsigned char data = 0xAB;
printf("\nJumlah bit 1 di 0xAB = %d\n", hitung_set_bit(data));
// 3. size_t untuk ukuran buffer
char buf[16] = "halo dunia";
size_t panjang = hitung_panjang_string(buf, sizeof buf);
printf("\nString: '%s'\n", buf);
printf("Panjang: %zu (size_t, bukan int)\n", panjang);
// 4. Overflow-safe addition
int a = INT_MAX - 10;
int b = 20;
int hasil;
if (aman_tambah(a, b, &hasil) == 0) {
printf("\n%d + %d = %d\n", a, b, hasil);
} else {
printf("\n%d + %d = OVERFLOW (deteksi aman)\n", a, b);
}
// 5. NULL vs 0
int *p = NULL;
if (p == NULL) {
printf("\nNULL masih sah di C89/C99/C11/C17/C23\n");
}
printf("NULL = %p\n", (void*)NULL);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -pedantic -o port_2 port_2.c
./port_2Output yang Dihasilkan:
unsigned char c=200 -> 200
unsigned char uc=200 -> 200
Jumlah bit 1 di 0xAB = 5
String: 'halo dunia'
Panjang: 10 (size_t, bukan int)
2147483637 + 20 = OVERFLOW (deteksi aman)
NULL masih sah di C89/C99/C11/C17/C23
NULL = (nil)Fungsi aman_tambah melakukan overflow check sebelum penjumlahan. Pola yang dipakai: jika b > 0 dan a > INT_MAX - b, maka a + b pasti overflow; jika b < 0 dan a < INT_MIN - b, pasti underflow. Cara ini memeriksa overflow tanpa benar-benar melakukannya, sehingga tidak melanggar undefined behavior. Fungsi hitung_panjang_string memakai size_t untuk parameter max_buf dan return value—size_t adalah tipe unsigned yang cukup besar untuk menyimpan ukuran objek apapun di memori (8 byte di x86-64), sehingga tidak overflow untuk file/buffer besar.
Checklist Audit Portabilitas #
Berikut checklist praktis untuk mengaudit kode C lama:
| Periksa | Masalah | Solusi |
|---|---|---|
int untuk ukuran buffer | Overflow jika objek >2GB | Ganti ke size_t |
char untuk byte data | Signness berbeda antar platform | Ganti ke unsigned char atau uint8_t |
long untuk pointer arithmetic | Ukuran berbeda LP64 vs LLP64 | Ganti ke intptr_t/uintptr_t |
int untuk time_t | Overflow di tahun 2038 | Ganti ke time_t atau int64_t |
Cast struct* ke char* untuk inspeksi byte | UB jika via union | Pakai memcpy atau cast unsigned char* langsung |
Asumsi sizeof(int) == 4 | Salah di platform 16-bit | Pakai sizeof(int32_t) == 4 |
| Signed overflow | UB, optimisasi compiler tak terduga | Cek eksplisit sebelum operasi |
Pengiriman struct langsung via send() | Endianness dan padding berbeda | Serialisasi byte-per-byte eksplisit |
| Magic number alamat/tipe | Tidak portabel antar platform | Pakai macro INT32_MAX, SIZE_MAX, PTRDIFF_MAX |
NULL vs 0 untuk pointer | Ambigu di C99 | Gunakan nullptr di C23 atau NULL untuk portabilitas |