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).

c
// 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:

bash
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_1 ekst_1.c
./ekst_1

Output yang Dihasilkan:

text
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: halo

Mari kita telaah baris-baris kode di atas:

  1. Baris 7-11 (struct __attribute__((packed)) Header { ... };): Attribute packed memberitahu compiler untuk tidak menyisipkan padding antar field. Tanpa packed, compiler akan menyisipkan 3 byte padding antara magic (4 byte) dan ukuran (4 byte, alignment 4) untuk efisiensi akses. Dengan packed, total sizeof adalah 10 byte (4+4+2) alih-alih 12. Berguna untuk header binary protocol dengan layout yang ketat.
  2. Baris 14-22 (__attribute__((format(printf, 1, 2)))): Attribute format memerintahkan compiler memeriksa format string argumen ke-1 dan argumen variadic mulai posisi ke-2 sesuai aturan printf. Jika kita salah menulis %d untuk string, compiler mengeluarkan warning -Wformat.
  3. 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—-pedantic memicu warning “ISO C forbids braced-groups within expressions”.
  4. Baris 33 (printf("sizeof(struct Header) packed = %zu\n", sizeof(h));): Hasil sizeof adalah 10 byte karena packed. Tanpa packed, hasilnya akan 12 byte karena padding.
  5. Baris 36 (int arr[16] __attribute__((aligned(64)));): Memaksa alamat arr kelipatan 64 byte—berguna untuk cache line alignment dan SIMD intrinsik. Tanpa __attribute__, alignment default int[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).

c
// 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:

bash
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_2 ekst_2.c
./ekst_2

Output yang Dihasilkan:

text
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  = 24

Array 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().

c
// 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:

bash
gcc -std=c11 -Wall -Wextra -pedantic -o ekst_3 ekst_3.c
./ekst_3
./ekst_3 a b c

Output yang Dihasilkan (./ekst_3 tanpa argumen tambahan):

text
ERROR: perlu argumen command line

Exit 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:

text
OK: jumlah argumen normal

Macro 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 GCCPadanan Standar CCatatan
__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