restrict adalah type qualifier yang diperkenalkan di C99 untuk memberi tahu compiler bahwa sebuah pointer adalah satu-satunya cara untuk mengakses objek yang ditunjuknya selama lifetime pointer tersebut. Dengan janji ini, compiler tahu bahwa tidak ada aliasing—dua pointer tidak menunjuk ke memori yang sama—sehingga optimasi yang lebih agresif dapat dilakukan. Tanpa restrict, compiler harus berhati-hati karena setiap akses pointer berpotensi mengubah nilai yang sama. Pada chapter ini kita akan memahami kontrak restrict, dampaknya pada optimasi, dan jebakan bila kontrak dilanggar.

Apa Itu Aliasing dan Mengapa restrict Penting #

Aliasing terjadi ketika dua pointer menunjuk ke lokasi memori yang sama. Contoh klasik: fungsi copy(dst, src, n) yang menyalin n elemen dari src ke dst. Bila dst dan src mengoverlap (misalnya dst mulai dari indeks 1 dan src dari indeks 0), iterasi dst[i] = src[i] dapat menghasilkan hasil yang salah karena nilai src[i] sudah diubah oleh iterasi sebelumnya. Compiler tidak bisa tahu apakah overlap terjadi, sehingga harus memuat ulang src[i] dari memori di setiap iterasi—menghalangi optimasi seperti vectorization.

c
#include <stdio.h>
#include <string.h>

/* restrict menjanjikan: src dan dst tidak mengoverlap */
void copy_int(int *restrict dst, const int *restrict src, size_t n) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

/* Tanpa restrict: compiler harus asumsikan kemungkinan aliasing */
void copy_int_norestrict(int *dst, const int *src, size_t n) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = src[i];
    }
}

int main(void) {
    int src[5] = {1, 2, 3, 4, 5};
    int dst[5] = {0};

    copy_int(dst, src, 5);
    printf("setelah copy: ");
    for (int i = 0; i < 5; i++) printf("%d ", dst[i]);
    printf("\n");

    return 0;
}

Output yang Dihasilkan:

text
setelah copy: 1 2 3 4 5 

Mari kita telaah baris-baris kode di atas:

  1. Baris 5 (void copy_int(int *restrict dst, const int *restrict src, size_t n)): kualifikasi restrict pada kedua pointer. Kontrak: selama pemanggilan fungsi ini, dst dan src tidak menunjuk memori yang sama, dan tidak ada pointer lain yang juga mengakses memori tersebut.
  2. Baris 6-8 (for (...) dst[i] = src[i];): implementasi naif. Tanpa restrict, compiler tidak bisa memindahkan src[i] ke register karena khawatir dst[i] = src[i] mungkin mengubah src[i+1] lewat aliasing.
  3. Baris 11-15 (copy_int_norestrict): signature tanpa restrict. Fungsi ini bekerja identik, tetapi compiler tidak bisa mengoptimalkan seagresif versi restrict.
  4. Baris 17-25 (main): pemakaian normal. dst dan src adalah array berbeda, jadi kontrak restrict terpenuhi. Hasil benar.

Optimasi yang Dimungkinkan restrict #

Dengan restrict, compiler dapat melakukan optimasi berikut:

  • Vectorization: memproses 4 elemen sekaligus lewat SIMD (SSE/AVX) karena dijamin tidak ada ketergantungan antar iterasi.
  • Common subexpression elimination: nilai src[i] yang dimuat sekali dapat dipakai ulang tanpa memuat ulang.
  • Reordering: compiler dapat menyusun ulang urutan baca-tulis untuk pipelining yang lebih baik.

Untuk melihat efeknya, bandingkan assembly yang dihasilkan dengan dan tanpa restrict:

bash
gcc -O2 -S -o copy_restrict.s test.c      # dengan restrict
gcc -O2 -S -o copy_norestrict.s test.c    # tanpa restrict

Pada praktiknya, versi restrict menghasilkan instruksi movdqu (vector) sedangkan versi tanpa restrict menghasilkan loop mov satu per satu. Selisih kecepatan bisa 2-4x untuk array besar.

c
#include <stdio.h>

/* Operasi yang aman dengan restrict */
void scale_add(int *restrict dst,
               const int *restrict a,
               const int *restrict b,
               int factor, size_t n) {
    for (size_t i = 0; i < n; i++) {
        dst[i] = a[i] + b[i] * factor;
    }
}

int main(void) {
    int data[5] = {10, 20, 30, 40, 50};
    int out[5]  = {0};

    /* Pemakaian normal: tidak ada overlap */
    scale_add(out, data, data, 2, 5);
    printf("hasil: ");
    for (int i = 0; i < 5; i++) printf("%d ", out[i]);
    printf("\n");

    return 0;
}

Output yang Dihasilkan:

text
hasil: 30 60 90 120 150 

Fungsi scale_add memakai tiga pointer restrict. Pemanggilan scale_add(out, data, data, 2, 5) mengoper data untuk parameter a dan b sekaligus. Apakah ini melanggar restrict? Tidak—a dan b sama-sama const, jadi keduanya hanya membaca. Kontrak restrict melarang dua pointer menulis/membaca memori yang sama bila salah satunya non-const. Bila a adalah const int *restrict dan b juga const int *restrict, keduanya boleh menunjuk lokasi yang sama karena tidak ada modifikasi.

Kapan Memakai restrict #

  • Hot loop yang melakukan banyak akses array: restrict memberi sinyal jelas ke compiler.
  • Wrapper fungsi standar seperti memcpy, strcpy yang sudah memakai restrict.
  • API yang dokumentasinya eksplisit melarang overlap: restrict membuat kontrak menjadi compiler-checkable.

Hindari restrict bila:

  • Fungsi benar-benar mendukung overlap (misal memmove sengaja tidak memakai restrict).
  • Codebase masih prototipe dan optimasi belum dibutuhkan—kerumitan tidak sepadan.
  • Compiler target tidak mendukung C99.