GCC dan Clang menyediakan ekstensi yang melampaui standar ISO C: __attribute__, statement expression, computed goto, __builtin_expect, nested function, dan banyak lagi. Ekstensi-ekstensi ini sangat berguna—kernel Linux, glibc, dan banyak library open-source memakainya secara intensif—tetapi setiap penggunaan ekstensi membuat kode tidak portabel ke compiler lain (mis. MSVC, vendor embedded). Bab ini menunjukkan ekstensi GCC yang paling sering dijumpai, menjelaskan apa yang dilakukan compiler saat memprosesnya, dan mendemonstrasikan bagaimana flag -pedantic membantu menangkap penggunaan ekstensi non-portabel. Kita juga akan melihat pola substitusi standar (mis. __attribute__((deprecated)) menjadi [[deprecated]] di C23) yang memungkinkan kita menulis kode yang lebih portabel tanpa kehilangan fungsionalitas.
__attribute__: Anotasi Fungsi, Tipe, dan Variabel #
GCC dan Clang menyediakan keyword __attribute__((...)) untuk menganotasi fungsi, tipe, dan variabel dengan metadata yang dimengerti compiler. Anotasi yang paling sering dipakai adalah format(printf, N, M) (cek format string printf-style), packed (hilangkan padding struct), aligned(N) (paksa alignment), deprecated("msg") (warning jika dipakai), dan noreturn (fungsi tidak kembali ke pemanggil).
// File: ekst_1.c - GCC extensions: __attribute__ dan statement expressions
#include <stdio.h>
#include <stdarg.h>
// __attribute__((packed)) - hilangkan padding
struct __attribute__((packed)) Header {
char magic[4]; // 4 byte
int ukuran; // 4 byte, langsung setelah magic tanpa padding
short checksum; // 2 byte
};
// __attribute__((format(printf, N, M))) - cek format string
static void log_info(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
static void log_info(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
putchar('\n');
}
int main(void) {
// Statement expression: blok yang menghasilkan nilai
int hasil = ({
int s = 0;
for (int i = 1; i <= 10; ++i) s += i;
s;
});
printf("Jumlah 1..10 = %d\n", hasil);
// sizeof struct packed
struct Header h;
printf("sizeof(struct Header) packed = %zu\n", sizeof(h));
// __attribute__ pada variabel
int arr[16] __attribute__((aligned(64)));
printf("Alamat arr aligned 64 : %p\n", (void*)arr);
// Format printf dicheck compiler
log_info("Nilai: %d, string: %s", 42, "halo");
return 0;
}Compile dengan -pedantic untuk melihat warning ekstensi:
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_1 ekst_1.c
./ekst_1Output yang Dihasilkan:
ekst_1.c: In function 'main':
ekst_1.c:25:17: warning: ISO C forbids braced-groups within expressions [-Wpedantic]
25 | int hasil = ({
| ^
Jumlah 1..10 = 55
sizeof(struct Header) packed = 10
Alamat arr aligned 64 : 0x7ffd077a9580
Nilai: 42, string: haloMari kita telaah baris-baris kode di atas:
- Baris 7-11 (
struct __attribute__((packed)) Header { ... };): Attributepackedmemberitahu compiler untuk tidak menyisipkan padding antar field. Tanpapacked, compiler akan menyisipkan 3 byte padding antaramagic(4 byte) danukuran(4 byte, alignment 4) untuk efisiensi akses. Denganpacked, totalsizeofadalah 10 byte (4+4+2) alih-alih 12. Berguna untuk header binary protocol dengan layout yang ketat. - Baris 14-22 (
__attribute__((format(printf, 1, 2)))): Attributeformatmemerintahkan compiler memeriksa format string argumen ke-1 dan argumen variadic mulai posisi ke-2 sesuai aturanprintf. Jika kita salah menulis%duntuk string, compiler mengeluarkan warning-Wformat. - Baris 26-30 (
int hasil = ({ ... s; });): Statement expression—blok({ ... })yang menghasilkan nilai. Ekspresi terakhir dalam blok (s;) menjadi nilai statement expression. Fitur ini tidak ada di standar C—-pedanticmemicu warning “ISO C forbids braced-groups within expressions”. - Baris 33 (
printf("sizeof(struct Header) packed = %zu\n", sizeof(h));): Hasilsizeofadalah 10 byte karenapacked. Tanpapacked, hasilnya akan 12 byte karena padding. - Baris 36 (
int arr[16] __attribute__((aligned(64)));): Memaksa alamatarrkelipatan 64 byte—berguna untuk cache line alignment dan SIMD intrinsik. Tanpa__attribute__, alignment defaultint[16]adalah 4 byte.
Computed Goto (Labels as Values) #
GCC memungkinkan kita mengambil alamat label dengan operator unary && dan lompat ke alamat tersebut dengan goto *expr. Fitur ini disebut “computed goto” atau “labels as values”, dan sering dipakai untuk implementasi interpreter atau state machine yang sangat cepat (mis. regex engine).
// File: ekst_2.c - GCC extensions: computed goto (labels as values)
#include <stdio.h>
static int hitung_dengan_goto(int op, int a, int b) {
static const void *tabel[] = { &&op_tambah, &&op_kurang, &&op_kali };
if (op < 0 || op > 2) return -1;
// Computed goto: lompat ke alamat label dari array
goto *tabel[op];
op_tambah:
return a + b;
op_kurang:
return a - b;
op_kali:
return a * b;
}
int main(void) {
printf("1 + 2 = %d\n", hitung_dengan_goto(0, 1, 2));
printf("5 - 3 = %d\n", hitung_dengan_goto(1, 5, 3));
printf("4 * 6 = %d\n", hitung_dengan_goto(2, 4, 6));
return 0;
}Compile dengan -pedantic:
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_2 ekst_2.c
./ekst_2Output yang Dihasilkan:
ekst_2.c: In function 'hitung_dengan_goto':
ekst_2.c:5:5: warning: taking the address of a label is non-standard [-Wpedantic]
5 | static const void *tabel[] = { &&op_tambah, &&op_kurang, &&op_kali };
| ^~~~~~
ekst_2.c:5:5: warning: taking the address of a label is non-standard [-Wpedantic]
ekst_2.c:5:5: warning: taking the address of a label is non-standard [-Wpedantic]
ekst_2.c:9:5: warning: ISO C forbids 'goto *expr;' [-Wpedantic]
9 | goto *tabel[op];
| ^~~~
1 + 2 = 3
5 - 3 = 2
4 * 6 = 24Array tabel menyimpan alamat tiga label sebagai void *. Operator &&op_tambah mengambil alamat label op_tambah (bukan address-of alamat variabel—&& di sini adalah operator unary GCC yang berbeda dari && logika AND). Statement goto *tabel[op] melompat ke alamat yang diambil dari array berdasarkan nilai op. Tanpa computed goto, kita perlu switch(op) { case 0: ...; case 1: ...; case 2: ...; } yang secara semantik setara tetapi tidak secepat computed goto untuk dispatch table besar.
__builtin_expect: Branch Prediction Hint #
GCC menyediakan __builtin_expect(expr, val) untuk memberi tahu compiler bahwa ekspresi expr kemungkinan besar bernilai val. Compiler lalu menyusun kode mesin agar branch yang “likely” berada di jalur lurus (fall-through), dan branch yang “unlikely” di lompat. Pola ini umum di kernel Linux lewat macro likely() dan unlikely().
// File: ekst_3.c - __builtin_expect untuk branch prediction
#include <stdio.h>
#include <stdbool.h>
// Macro umum di kernel Linux:
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
static int parse_argc(int argc) {
// branch yang sangat jarang - kasih hint ke compiler
if (unlikely(argc < 2)) {
printf("ERROR: perlu argumen command line\n");
return -1;
}
if (likely(argc <= 10)) {
printf("OK: jumlah argumen normal\n");
return 0;
}
printf("WARNING: terlalu banyak argumen\n");
return 1;
}
int main(int argc, char **argv) {
(void)argv;
return parse_argc(argc);
}Compile dan jalankan dengan jumlah argumen berbeda:
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_3 ekst_3.c
./ekst_3
./ekst_3 a b cOutput yang Dihasilkan (./ekst_3 tanpa argumen tambahan):
ERROR: perlu argumen command lineExit code program di atas adalah 255 (karena return -1 di-interpretasikan sebagai unsigned 8-bit 0xFF). Untuk ./ekst_3 a b c (argc=4), outputnya:
OK: jumlah argumen normalMacro likely(x) dan unlikely(x) memetakan ke __builtin_expect(!!(x), 1) dan __builtin_expect(!!(x), 0). Operator !! memaksa nilai x menjadi 0 atau 1 terlepas dari tipenya. Compiler lalu menyusun kode mesin: cabang unlikely ditempatkan jauh dari hot path (mungkin di akhir fungsi), sedangkan cabang likely tetap di jalur lurus untuk meminimalkan cache miss di branch predictor CPU.
Substitusi Standar untuk Ekstensi GCC #
C23 mengadopsi beberapa ekstensi GCC sebagai fitur standar, sehingga kita dapat menulis kode portabel tanpa kehilangan fungsionalitas. Tabel berikut memetakan ekstensi GCC ke padanan standar C11/C23:
| Ekstensi GCC | Padanan Standar C | Catatan |
|---|---|---|
__attribute__((deprecated("msg"))) | [[deprecated("msg")]] (C23) | Sintaks berbeda, semantik sama |
__attribute__((noreturn)) | _Noreturn (C11) / [[noreturn]] (C23) | C11 sudah punya keyword |
__attribute__((unused)) | [[maybe_unused]] (C23) | C23 mengadopsi |
__attribute__((warn_unused_result)) | [[nodiscard]] (C23) | C23 mengadopsi |
__attribute__((aligned(N))) | _Alignas(N) (C11) | C11 mengadopsi |
__attribute__((format(printf,N,M))) | (tidak ada padanan) | Tetap ekstensi GCC/Clang |
__builtin_types_compatible_p(t1,t2) | _Generic (C11) | C11 menggantikan dengan semantik berbeda |
typeof(expr) (GCC) | typeof(expr) (C23) | C23 mengadopsi |
Statement expression ({ ... }) | (tidak ada padanan) | Tetap ekstensi GCC/Clang |
Computed goto goto *expr | (tidak ada padanan) | Tetap ekstensi GCC/Clang |
__builtin_expect | (tidak ada padanan) | Tetap ekstensi GCC/Clang |