Ketika kita menulis library yang ditargetkan untuk banyak compiler (GCC, Clang, MSVC, vendor embedded), kita tidak bisa berasumsi semua fitur tersedia. Standar C menyediakan beberapa mekanisme deteksi fitur: macro __STDC_VERSION__ untuk versi standar, macro __STDC_NO_ATOMICS/__STDC_NO_THREADS untuk fitur opsional C11, operator preprocessor __has_include (sejak C23, didukung GCC sejak 5.1) untuk cek ketersediaan header, dan __has_builtin/__has_attribute (ekstensi GCC/Clang) untuk cek ekstensi compiler. Bab ini menunjukkan pola penulisan conditional feature support yang membuat kode dapat beradaptasi otomatis dengan compiler yang dipakai—memakai fitur modern bila tersedia, fallback ke implementasi alternatif bila tidak. Kita juga akan melihat strategi penulisan header portabilitas yang dapat di-include di semua compiler tanpa menyebabkan error.
Deteksi via __STDC_VERSION__ #
Cara paling portabel untuk mendeteksi versi standar adalah memeriksa macro __STDC_VERSION__. Macro ini didefinisikan sejak C95; di mode C89/C90, macro ini tidak terdefinisi. Program berikut memeriksa ketersediaan fitur C99, C11, C17, dan C23 secara berurutan:
// File: kond_1.c - __STDC_VERSION__ dan feature test macro
#include <stdio.h>
int main(void) {
printf("Deteksi fitur berdasarkan __STDC_VERSION__:\n");
#if defined(__STDC_VERSION__)
printf(" __STDC_VERSION__ = %ldL\n", __STDC_VERSION__);
#else
printf(" __STDC_VERSION__ tidak terdefinisi (mode C89/C90)\n");
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
printf(" // komentar, mixed declaration : tersedia (C99+)\n");
#else
printf(" // komentar, mixed declaration : tidak tersedia (C89)\n");
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
printf(" _Generic, _Static_assert : tersedia (C11+)\n");
#ifdef __STDC_NO_ATOMICS
printf(" atomics : TIDAK didukung\n");
#else
printf(" atomics : didukung\n");
#endif
#ifdef __STDC_NO_THREADS
printf(" threads.h : TIDAK didukung\n");
#else
printf(" threads.h : didukung\n");
#endif
#else
printf(" _Generic, _Static_assert : tidak tersedia (pre-C11)\n");
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201710L
printf(" ATOMIC_VAR_INIT obsolete : ya (C17+)\n");
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
printf(" nullptr, constexpr, [[nodiscard]]: tersedia (C23)\n");
#else
printf(" nullptr, constexpr, [[nodiscard]]: belum final C23\n");
#endif
return 0;
}Compile dengan beberapa flag dan amati perbedaannya:
gcc -std=c11 -Wall -Wextra -pedantic -o kond_1_c11 kond_1.c && ./kond_1_c11
gcc -std=c17 -Wall -Wextra -pedantic -o kond_1_c17 kond_1.c && ./kond_1_c17
gcc -std=c2x -Wall -Wextra -pedantic -o kond_1_c2x kond_1.c && ./kond_1_c2xOutput yang Dihasilkan (gcc -std=c11):
Deteksi fitur berdasarkan __STDC_VERSION__:
__STDC_VERSION__ = 201112L
// komentar, mixed declaration : tersedia (C99+)
_Generic, _Static_assert : tersedia (C11+)
atomics : didukung
threads.h : didukung
nullptr, constexpr, [[nodiscard]]: belum final C23Output yang Dihasilkan (gcc -std=c17):
Deteksi fitur berdasarkan __STDC_VERSION__:
__STDC_VERSION__ = 201710L
// komentar, mixed declaration : tersedia (C99+)
_Generic, _Static_assert : tersedia (C11+)
atomics : didukung
threads.h : didukung
ATOMIC_VAR_INIT obsolete : ya (C17+)
nullptr, constexpr, [[nodiscard]]: belum final C23Mari kita telaah baris-baris kode di atas:
- Baris 7-11 (
#if defined(__STDC_VERSION__) ... #endif): Cek apakah macro terdefinisi sebelum mengakses nilainya. Di mode C89,__STDC_VERSION__tidak ada; mengakses macro yang tidak terdefinisi di#ifakan dianggap0, tetapi lebih aman pakaidefined()eksplisit. - Baris 13-17 (
#if ... __STDC_VERSION__ >= 199901L): Cek ketersediaan C99. Angka199901Ladalah kodeYYYYMMLuntuk C99 (Januari 1999). Bila compiler lebih baru, cabang ini aktif dan kita tahu//komentar serta mixed declaration dapat dipakai. - Baris 19-32 (
#if ... __STDC_VERSION__ >= 201112L): Cek ketersediaan C11. Di dalam cabang ini, kita juga cek__STDC_NO_ATOMICSdan__STDC_NO_THREADS—dua macro yang didefinisikan compiler jika fitur tersebut tidak didukung. Pada GCC modern, keduanya tidak didefinisikan, yang berarti fitur tersedia. - Baris 34-36 (
#if ... __STDC_VERSION__ >= 201710L): Cek C17. Hanya menampilkan informasi tentangATOMIC_VAR_INITyang obsolete di C17. - Baris 38-42 (
#if ... __STDC_VERSION__ >= 202311L): Cek C23 final. GCC 14 masih memberi202000L(placeholder), sehingga cabang ini tidak aktif—program akan melaporkan “belum final C23” meskipun kompilernya mendukung sebagian besar fitur C23.
__has_include dan __has_builtin #
Preprocessor C23 menambahkan operator __has_include("header") yang mengembalikan 1 bila header tersedia, 0 bila tidak. Sebelum C23, GCC dan Clang sudah mendukung ini sebagai ekstensi. Pola ini memungkinkan kode memilih header alternatif tanpa menyebabkan compile error. GCC dan Clang juga menyediakan __has_builtin(name) dan __has_attribute(name) untuk mengecek ekstensi compiler:
// File: kond_2.c - __has_include dan __has_builtin
#include <stdio.h>
// __has_include: cek apakah header tersedia tanpa menyebabkan error
#if defined(__has_include)
# if __has_include(<stdatomic.h>)
# include <stdatomic.h>
# define PUNYA_ATOMIC 1
# else
# define PUNYA_ATOMIC 0
# endif
# if __has_include(<threads.h>)
# include <threads.h>
# define PUNYA_THREADS 1
# else
# define PUNYA_THREADS 0
# endif
# if __has_include(<sys/epoll.h>)
# define PUNYA_EPOLL 1
# else
# define PUNYA_EPOLL 0
# endif
#else
# define PUNYA_ATOMIC 0
# define PUNYA_THREADS 0
# define PUNYA_EPOLL 0
#endif
// __has_builtin: cek apakah GCC builtin tersedia
#if defined(__has_builtin)
# if __has_builtin(__builtin_expect)
# define PUNYA_BUILTIN_EXPECT 1
# else
# define PUNYA_BUILTIN_EXPECT 0
# endif
# if __has_builtin(__builtin_popcount)
# define PUNYA_BUILTIN_POPCOUNT 1
# else
# define PUNYA_BUILTIN_POPCOUNT 0
# endif
#else
# define PUNYA_BUILTIN_EXPECT 0
# define PUNYA_BUILTIN_POPCOUNT 0
#endif
// __has_attribute: cek apakah GCC attribute tersedia
#if defined(__has_attribute)
# if __has_attribute(packed)
# define PUNYA_ATTR_PACKED 1
# else
# define PUNYA_ATTR_PACKED 0
# endif
#else
# define PUNYA_ATTR_PACKED 0
#endif
int main(void) {
printf("Hasil deteksi fitur:\n");
printf(" PUNYA_ATOMIC : %d\n", PUNYA_ATOMIC);
printf(" PUNYA_THREADS : %d\n", PUNYA_THREADS);
printf(" PUNYA_EPOLL : %d\n", PUNYA_EPOLL);
printf(" PUNYA_BUILTIN_EXPECT : %d\n", PUNYA_BUILTIN_EXPECT);
printf(" PUNYA_BUILTIN_POPCOUNT: %d\n", PUNYA_BUILTIN_POPCOUNT);
printf(" PUNYA_ATTR_PACKED : %d\n", PUNYA_ATTR_PACKED);
#if PUNYA_BUILTIN_POPCOUNT
printf("\nContoh __builtin_popcount(0xAB) = %d (jumlah bit 1)\n",
__builtin_popcount(0xAB));
#endif
return 0;
}Compile dan jalankan:
gcc -std=c11 -Wall -Wextra -pedantic -o kond_2 kond_2.c
./kond_2Output yang Dihasilkan:
Hasil deteksi fitur:
PUNYA_ATOMIC : 1
PUNYA_THREADS : 1
PUNYA_EPOLL : 1
PUNYA_BUILTIN_EXPECT : 1
PUNYA_BUILTIN_POPCOUNT: 1
PUNYA_ATTR_PACKED : 1
Contoh __builtin_popcount(0xAB) = 5 (jumlah bit 1)Pada contoh di atas, __has_include(<sys/epoll.h>) mengembalikan 1 di Linux karena header epoll tersedia, tetapi akan mengembalikan 0 di macOS atau Windows. Dengan pola ini, kode kita dapat memilih implementasi berbasis epoll di Linux dan fallback ke select/kqueue di platform lain. __has_builtin(__builtin_popcount) mengembalikan 1 di GCC/Clang; di compiler yang tidak mendukung, kita dapat fallback ke implementasi popcount manual dengan loop.
Pola Header Portabilitas #
Strategi praktis untuk library lintas compiler adalah membuat header portabilitas (mis. portab.h) yang mendeteksi fitur sekali dan mengekspor macro sederhana. Header lain lalu memakai macro tersebut tanpa perlu pemeriksaan #ifdef berulang. Berikut sketsa header portabilitas:
// File: portab.h - Header portabilitas lintas compiler
#ifndef PORTAB_H
#define PORTAB_H
/* Versi standar yang aktif */
#if defined(__STDC_VERSION__)
# define PORTAB_STD_VERSION (__STDC_VERSION__)
#else
# define PORTAB_STD_VERSION 199000L /* Asumsi C90 */
#endif
/* Pilihan nullptr */
#if PORTAB_STD_VERSION >= 202311L
# define PORTAB_NULL nullptr
#elif defined(__cplusplus) && __cplusplus >= 201103L
# define PORTAB_NULL nullptr
#else
# define PORTAB_NULL ((void*)0)
#endif
/* Pilihan noreturn */
#if PORTAB_STD_VERSION >= 202311L
# define PORTAB_NORETURN [[noreturn]]
#elif PORTAB_STD_VERSION >= 201112L
# define PORTAB_NORETURN _Noreturn
#elif defined(__GNUC__)
# define PORTAB_NORETURN __attribute__((noreturn))
#else
# define PORTAB_NORETURN
#endif
/* Pilihan deprecated */
#if PORTAB_STD_VERSION >= 202311L
# define PORTAB_DEPRECATED(msg) [[deprecated(msg)]]
#elif defined(__GNUC__)
# define PORTAB_DEPRECATED(msg) __attribute__((deprecated(msg)))
#else
# define PORTAB_DEPRECATED(msg)
#endif
/* Pilihan likely/unlikely */
#if defined(__GNUC__)
# define PORTAB_LIKELY(x) __builtin_expect(!!(x), 1)
# define PORTAB_UNLIKELY(x) __builtin_expect(!!(x), 0)
#else
# define PORTAB_LIKELY(x) (x)
# define PORTAB_UNLIKELY(x) (x)
#endif
#endif /* PORTAB_H */Header di atas dapat di-include di compiler manapun (GCC, Clang, MSVC, SDCC). Macro PORTAB_NULL otomatis memilih nullptr di C23/C++11 atau ((void*)0) di C89. Macro PORTAB_NORETURN memilih [[noreturn]] di C23, _Noreturn di C11, atau __attribute__((noreturn)) di GCC tua. Header seperti ini biasanya dipakai sebagai dasar untuk library lintas compiler seperti SDL, libuv, atau c-ares.