Portability adalah kemampuan kode C untuk dikompilasi dan dijalankan di berbagai platform—komputer 32 vs 64 bit, Linux vs Windows, x86 vs ARM—dengan hasil yang konsisten. C terkenal sebagai bahasa portabel, tetapi dalam praktik banyak kode C hanya berjalan di satu platform karena asumsi yang tidak sengaja dibawa. Bahaya terbesar adalah mengasumsikan sizeof(int), endianness, atau alignment tertentu. Pada chapter ini kita akan mempelajari praktik menulis kode portabel dengan tipe dari <stdint.h>, format specifier dari <inttypes.h>, dan konversi endianness eksplisit.
Tipe Eksplisit dengan <stdint.h> #
Header <stdint.h> (C99) menyediakan tipe integer dengan ukuran pasti: int8_t, int16_t, int32_t, int64_t, dan versi unsigned-nya (uint8_t sampai uint64_t). Tipe ini membebaskan kita dari asumsi sizeof(int) == 4 yang berlaku di sebagian besar platform tetapi tidak dijamin.
#include <stdio.h>
#include <stdint.h>
/* Header struct jaringan yang harus portabel */
typedef struct {
uint16_t magic; /* pasti 16 bit */
uint32_t length; /* pasti 32 bit */
int16_t temperature; /* pasti 16 bit signed */
} PacketHeader;
/* Konversi endianness manual (portabel, tidak bergantung platform) */
uint32_t host_to_network_32(uint32_t h) {
return ((h & 0xFF000000) >> 24) |
((h & 0x00FF0000) >> 8) |
((h & 0x0000FF00) << 8) |
((h & 0x000000FF) << 24);
}
int main(void) {
printf("sizeof(PacketHeader) = %zu\n", sizeof(PacketHeader));
printf("sizeof(uint16_t) = %zu, sizeof(uint32_t) = %zu\n",
sizeof(uint16_t), sizeof(uint32_t));
uint32_t x = 0x12345678;
uint32_t net = host_to_network_32(x);
printf("host: 0x%08X\n", x);
printf("network: 0x%08X\n", net);
return 0;
}Output yang Dihasilkan:
sizeof(PacketHeader) = 12
sizeof(uint16_t) = 2, sizeof(uint32_t) = 4
host: 0x12345678
network: 0x78563412Mari kita telaah baris-baris kode di atas:
- Baris 6-11 (
typedef struct { uint16_t magic; uint32_t length; ... }): struct header paket jaringan. Tipeuint16_tdanuint32_tmenjamin ukuran tetap 2 dan 4 byte, terlepas dari platform. Pada embedded 16-bit sekalipun, ukuran ini tidak berubah. - Baris 14-19 (
host_to_network_32): konversi endianness manual. Fungsi ini bekerja di platform apa pun karena hanya menggunakan operasi bit—tidak bergantung pada arsitektur host. Operasi>> 24mengisolasi byte paling signifikan, lalu<< 24menempatkannya di posisi yang sesuai untuk output. - Baris 21-22 (
sizeof(PacketHeader)dansizeof(uint16_t)): hasil output menunjukkansizeof(PacketHeader) = 12. Pada x86-64, struct 2 + 4 + 2 = 8 byte ditambah padding menjadi 12 byte (alignmentuint32_t= 4). Pada platform dengan alignment berbeda, ukuran bisa berbeda—gunakan#pragma packbila layout harus persis. - Baris 26-28 (host vs network):
0x12345678di little-endian menjadi0x78563412setelah konversi ke network order (big-endian). Konversi manual ini menjamin format wire tidak bergantung pada endianness host.
Tipe fast, least, dan Format Specifier Portabel #
Selain tipe eksplisit (int32_t dll), <stdint.h> menyediakan tipe int_leastN_t (minimal N bit, ukuran sekecil mungkin) dan int_fastN_t (minimal N bit, dioptimasi untuk performa). Tipe intptr_t/uintptr_t cukup besar untuk menyimpan pointer. Format specifier portabel tersedia di <inttypes.h>.
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <limits.h>
/* Tipe fast: minimal N bit, dioptimasi untuk performa */
int main(void) {
printf("== tipe eksplisit (pasti ukuran) ==\n");
printf("sizeof(int8_t) = %zu, INT8_MIN=%d INT8_MAX=%d\n",
sizeof(int8_t), INT8_MIN, INT8_MAX);
printf("sizeof(int16_t) = %zu, INT16_MIN=%d INT16_MAX=%d\n",
sizeof(int16_t), INT16_MIN, INT16_MAX);
printf("sizeof(int32_t) = %zu, INT32_MIN=%d INT32_MAX=%d\n",
sizeof(int32_t), INT32_MIN, INT32_MAX);
printf("sizeof(int64_t) = %zu\n", sizeof(int64_t));
printf("\n== tipe fast (minimal ukuran, dioptimasi) ==\n");
printf("sizeof(int_fast8_t) = %zu\n", sizeof(int_fast8_t));
printf("sizeof(int_fast16_t) = %zu\n", sizeof(int_fast16_t));
printf("sizeof(int_fast32_t) = %zu\n", sizeof(int_fast32_t));
printf("\n== tipe least (minimal ukuran, terkecil) ==\n");
printf("sizeof(int_least8_t) = %zu\n", sizeof(int_least8_t));
printf("sizeof(int_least16_t) = %zu\n", sizeof(int_least16_t));
printf("\n== intptr_t (pointer ke int) ==\n");
printf("sizeof(intptr_t) = %zu\n", sizeof(intptr_t));
printf("sizeof(uintptr_t) = %zu\n", sizeof(uintptr_t));
/* PRIu32 / PRId64 dari <inttypes.h> untuk printf yang portabel */
int32_t suhu = -5;
uint64_t populasi = 270000000ULL;
printf("\nFormat portabel: suhu=%" PRId32 ", populasi=%" PRIu64 "\n",
suhu, populasi);
return 0;
}Output yang Dihasilkan:
== tipe eksplisit (pasti ukuran) ==
sizeof(int8_t) = 1, INT8_MIN=-128 INT8_MAX=127
sizeof(int16_t) = 2, INT16_MIN=-32768 INT16_MAX=32767
sizeof(int32_t) = 4, INT32_MIN=-2147483648 INT32_MAX=2147483647
sizeof(int64_t) = 8
== tipe fast (minimal ukuran, dioptimasi) ==
sizeof(int_fast8_t) = 1
sizeof(int_fast16_t) = 8
sizeof(int_fast32_t) = 8
== tipe least (minimal ukuran, terkecil) ==
sizeof(int_least8_t) = 1
sizeof(int_least16_t) = 2
== intptr_t (pointer ke int) ==
sizeof(intptr_t) = 8
sizeof(uintptr_t) = 8
Format portabel: suhu=-5, populasi=270000000Perhatikan bahwa int_fast16_t berukuran 8 byte di x86-64, bukan 2 byte. Compiler memilih ukuran yang paling cepat diakses—di x86-64, akses ke 8 byte (64-bit register) sama cepatnya dengan 2 byte, dan seringkali lebih cepat karena tidak perlu ekstensi tanda. Tipe int_least16_t tetap 2 byte karena memang yang paling kecil.
Format specifier PRId32 dan PRIu64 memastikan printf memakai specifier yang benar untuk tipe yang sesuai. Pada platform di mana int32_t adalah int, PRId32 diperluas menjadi "d". Pada platform di mana int32_t adalah long, PRId32 diperluas menjadi "ld". String literal bersebelahan otomatis digabungkan compiler sehingga printf("%" PRId32, x) sama dengan printf("%d", x) atau printf("%ld", x) tergantung platform.
Daftar Praktik Portabel #
- Gunakan
<stdint.h>:int32_talih-alihlong,uint8_talih-alihunsigned char. - Gunakan
<inttypes.h>:PRId32,PRIu64,SCNd32untuk format specifier portabel. - Gunakan
<stdbool.h>:boolalih-alih_Bool,true/falsealih-alih1/0. - Hindari asumsi ukuran: jangan tulis
int x = sizeof(long);dengan harapan nilai tertentu. - Hindari asumsi endianness: gunakan
htonl/ntohlatau konversi byte eksplisit. - Hindari asumsi alignment: jangan cast
char *keint *tanpa menyalin. - Hindari compiler extension: jangan pakai
__attribute__,__builtin, atau__synctanpa fallback. - Pakai feature test macro:
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112Luntuk C11.
Portabilitas di Tingkat Lanjut #
Untuk kode yang menargetkan platform khusus (embedded, real-time, atau sistem operasi tertentu), pertimbangkan:
- Conditional compilation (
#ifdef): bila perlu kode berbeda per platform. Batasi penggunaan—terlalu banyak#ifdefmembuat kode sulit dibaca. - Abstraction layer: bungkus detail platform dalam API internal. Implementasi berbeda per platform, tetapi API publik seragam.
- Pengujian cross-compile: kompilasi ke target berbeda (ARM, MIPS, RISC-V) dengan toolchain seperti
arm-linux-gnueabihf-gcc.