Chapter-chapter sebelumnya memakai API POSIX (pthread, fork, pipe, signal) yang berjalan di Linux, macOS, dan BSD. Windows memiliki API sendiri (CreateThread, CreateProcess, CreatePipe, SetConsoleCtrlHandler) yang tidak kompatibel. Untuk menulis kode sistem yang berjalan di kedua platform, kita memakai conditional compilation lewat macro #ifdef _WIN32 dan #ifdef __linux__. Pada chapter ini kita menulis tiga demo konkret: pertama mendeteksi platform saat compile, kedua membungkus thread+mutex dengan typedef dan helper function, ketiga menyediakan sleep milidetik yang portabel. Pola ini adalah inti dari library portabel seperti SDL, libuv, dan bagian inti banyak library open source.
Mendeteksi Platform saat Compile #
Compiler C mengatur beberapa macro otomatis berdasarkan target. Tiga macro paling sering dipakai:
__linux__: didefinisikan bila target adalah Linux_WIN32: didefinisikan bila target adalah Windows (32-bit atau 64-bit)__APPLE__: didefinisikan bila target adalah macOS atau iOS__STDC_VERSION__: versi standar C yang dipatuhi compiler (201112Luntuk C11,202311Luntuk C23)
/* File: deteksi_platform.c - mengetahui platform saat compile */
#include <stdio.h>
int main(void) {
printf("Deteksi platform:\n");
printf(" __linux__ = %d\n",
#ifdef __linux__
1
#else
0
#endif
);
printf(" _WIN32 = %d\n",
#ifdef _WIN32
1
#else
0
#endif
);
printf(" __APPLE__ = %d\n",
#ifdef __APPLE__
1
#else
0
#endif
);
printf(" __STDC_VERSION__ = %ldL\n", __STDC_VERSION__);
printf("\nDetail platform:\n");
#ifdef __linux__
printf(" Berjalan di Linux (POSIX)\n");
#elif defined(_WIN32)
printf(" Berjalan di Windows (Win32)\n");
#elif defined(__APPLE__)
printf(" Berjalan di macOS (POSIX/BSD)\n");
#else
printf(" Platform tidak dikenali\n");
#endif
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o deteksi_platform deteksi_platform.c
./deteksi_platformOutput yang Dihasilkan:
Deteksi platform:
__linux__ = 1
_WIN32 = 0
__APPLE__ = 0
__STDC_VERSION__ = 201112L
Detail platform:
Berjalan di Linux (POSIX)Mari kita telaah baris-baris kode di atas:
- Baris 6-9 (
#ifdef __linux__ ... 1 ... #else ... 0 ... #endif): Pola ini mengkonversi keberadaan macro menjadi literal1atau0sehingga bisa dicetak denganprintf. Tanpa trik ini,#ifdefhanya bekerja di tingkat preprocessor, tidak bisa dipakai sebagai expression di kode C biasa. __STDC_VERSION__: Macro standar C. Nilai201112Lberarti compiler mematuhi C11. GCC 14 juga mendukung C17 (201710L) dan C23 (202311L) bila dipanggil dengan-std=c17atau-std=c23.- Baris 30-39 (
#ifdef __linux__ ... #elif defined(_WIN32) ... #elif defined(__APPLE__) ... #endif): Pola percabangan compile-time. Penting: gunakan#elif defined(...)(dengan tanda kurung), bukan#elif __APPLE__, karena#eliftidak otomatis mengecekdefined()seperti#ifdef. - Catatan untuk
_WIN32: Macro ini didefinisikan untuk kedua Windows 32-bit dan 64-bit. Untuk membedakan, periksa_WIN64setelah_WIN32. Untuk kebanyakan kode,_WIN32sudah cukup—API Win32 sama untuk keduanya.
Membungkus Thread dan Mutex dengan Typedef #
Untuk menyembunyikan perbedaan API thread, kita buat lapisan abstraksi sederhana: typedef untuk tipe thread dan mutex, plus fungsi pembantu (thread_create, thread_join, mutex_lock, dll.). Kode pemakai tidak peduli platform—ia hanya memanggil fungsi pembantu.
/* File: thread_portabel.c - thread dan mutex lintas platform */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
typedef HANDLE thread_t;
typedef CRITICAL_SECTION mutex_t;
static int thread_create(thread_t *t, LPTHREAD_START_ROUTINE fn, void *arg) {
*t = CreateThread(NULL, 0, fn, arg, 0, NULL);
return (*t == NULL) ? -1 : 0;
}
static int thread_join(thread_t t) {
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
return 0;
}
static void mutex_init(mutex_t *m) { InitializeCriticalSection(m); }
static void mutex_destroy(mutex_t *m) { DeleteCriticalSection(m); }
static void mutex_lock(mutex_t *m) { EnterCriticalSection(m); }
static void mutex_unlock(mutex_t *m) { LeaveCriticalSection(m); }
#else
/* POSIX */
#include <pthread.h>
typedef pthread_t thread_t;
typedef pthread_mutex_t mutex_t;
static int thread_create(thread_t *t, void *(*fn)(void *), void *arg) {
return pthread_create(t, NULL, fn, arg);
}
static int thread_join(thread_t t) {
return pthread_join(t, NULL);
}
static void mutex_init(mutex_t *m) { *m = (pthread_mutex_t) PTHREAD_MUTEX_INITIALIZER; }
static void mutex_destroy(mutex_t *m) { pthread_mutex_destroy(m); }
static void mutex_lock(mutex_t *m) { pthread_mutex_lock(m); }
static void mutex_unlock(mutex_t *m) { pthread_mutex_unlock(m); }
#endif
static long counter = 0;
static mutex_t counter_mtx;
#ifdef _WIN32
static DWORD WINAPI naikkan(LPVOID arg) {
#else
static void *naikkan(void *arg) {
#endif
(void) arg;
for (int i = 0; i < 100000; i++) {
mutex_lock(&counter_mtx);
counter++;
mutex_unlock(&counter_mtx);
}
return 0;
}
int main(void) {
mutex_init(&counter_mtx);
thread_t t1, t2;
thread_create(&t1, naikkan, NULL);
thread_create(&t2, naikkan, NULL);
thread_join(t1);
thread_join(t2);
printf("Counter (harus 200000): %ld\n", counter);
mutex_destroy(&counter_mtx);
return 0;
}Compile di Linux dengan -pthread:
gcc -std=c11 -Wall -Wextra -pthread -o thread_portabel thread_portabel.c
./thread_portabelOutput yang Dihasilkan:
Counter (harus 200000): 200000Mari kita telaah baris-baris kode di atas:
- Baris 5 (
#ifdef _WIN32): Pemisah utama. Bila compiler mendeteksi target Windows, blok Windows diaktifkan. Bila tidak (Linux, macOS, BSD), blok POSIX diaktifkan. - Baris 7-8 (
typedef HANDLE thread_t; typedef CRITICAL_SECTION mutex_t;): Di Windows,HANDLEadalah tipe untuk thread, danCRITICAL_SECTIONadalah tipe untuk mutex. Kita alias keduanya ke nama yang konsisten:thread_tdanmutex_t. - Baris 10-15 (
thread_create,thread_joinuntuk Windows): MembungkusCreateThreaddanWaitForSingleObject. Signature fungsi pembantu sengaja dibuat mirip POSIX: mengembalikan0saat sukses,-1saat gagal. - Baris 27-30 (
typedef pthread_t thread_t;): Di POSIX,pthread_tdanpthread_mutex_tadalah tipe asli. Kita alias ke nama yang sama dengan blok Windows—kode di bawah#endif(yaitunaikkandanmain) dapat memakai nama generik. - Baris 41-46 (
naikkan): Fungsi thread perlu signature berbeda untuk Windows (DWORD WINAPI(LPVOID)) vs POSIX (void *(void *)).#ifdefdi sini hanya memilih signature; isi fungsi sama. Return0di Windows danNULLdi POSIX, tetapi kode main membaca keduanya sama. - Baris 60-65 (
main): Setelah lapisan abstraksi, kode main tidak punya satu pun#ifdef—ia memanggilthread_create,mutex_lock, dll. yang implementasinya berbeda tapi signature sama.
Sleep Milidetik yang Portabel #
API sleep berbeda jauh antara POSIX dan Windows. POSIX punya nanosleep (resolusi nanodetik), Windows punya Sleep (resolusi milidetik). Kita bungkus keduanya dengan fungsi jeda_ms tunggal.
/* File: sleep_portabel.c - tidur milidetik lintas platform */
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
static void jeda_ms(int ms) {
Sleep(ms); /* Windows: milidetik */
}
#else
static void jeda_ms(int ms) {
struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L };
nanosleep(&ts, NULL);
}
#endif
int main(void) {
printf("Tunggu 100 ms...\n");
fflush(stdout);
struct timespec t0, t1;
clock_gettime(CLOCK_MONOTONIC, &t0);
jeda_ms(100);
clock_gettime(CLOCK_MONOTONIC, &t1);
double elapsed = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
printf("Waktu sebenarnya: %.3f detik\n", elapsed);
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -o sleep_portabel sleep_portabel.c
./sleep_portabelOutput yang Dihasilkan:
Tunggu 100 ms...
Waktu sebenarnya: 0.100 detikMari kita telaah baris-baris kode di atas:
- Baris 7-9 (
#ifdef _WIN32 ... Sleep(ms)): Di Windows,Sleepmenerima argumen dalam milidetik. Implementasi langsung—tidak ada konversi. - Baris 11-13 (
struct timespec ts = { ms / 1000, ... }): POSIXnanosleepmenerimastruct timespecdengan detik dan nanodetik terpisah. Kita konversi: bagian detik =ms / 1000, bagian nanodetik = sisa milidetik × 1 juta. - Baris 18-26 (
main): Kode yang memakaijeda_mstidak punya#ifdef—ia bisa dikompilasi ulang untuk Windows atau Linux tanpa perubahan. Inilah inti portabilitas: kompleksitas platform disembunyikan di lapisan abstraksi. - Pengukuran waktu dengan
clock_gettime(CLOCK_MONOTONIC, ...): Hanya POSIX. Untuk kode yang juga perlu berjalan di Windows, bungkus juga fungsi pengukuran waktu—QueryPerformanceCounterdi Windows,clock_gettimedi POSIX. Atau gunakan C11<time.h>dengantimespec_get.
Strategi Portabilitas Tingkat Lanjut #
Untuk proyek skala produksi, tiga pendekatan tambahan sering dipakai:
- C11 threads (
<threads.h>): Standar C11 mendefinisikanthrd_create,mtx_lock,cnd_wait, dan sejenisnya. Sebagian besar compiler modern mendukungnya, tetapi MSVC lama tidak. Cocok bila Anda dapat membatasi target compiler. - Library abstraksi pihak ketiga: Library seperti libuv, SDL2, atau Boost (C++) menyediakan API konsisten lintas platform. Trade-off: dependensi eksternal dan ukuran biner lebih besar.
- CMake
try_compiledan feature detection: Build system CMake dapat mendeteksi apakah suatu fungsi tersedia (check_function_exists) dan men-generate header konfigurasi. Pola ini dipakai oleh proyek open source besar seperti OpenSSL dan curl.