restrict 限定詞(the restrict qualifier)
/ ree-STRIKT /
兩個指標若可能指向同一塊記憶體,就稱為「別名(alias)」。當函式接收兩個指標時,編譯器通常必須假設它們可能重疊,這迫使它重新載入原本可保留在暫存器中的值——犧牲了速度。C99 加入的 restrict 限定詞是你做出的一個「承諾」:『在這個指標的生命期內,它所指的物件「只」透過這個指標(或由它衍生的指標)來存取。』這個承諾讓最佳化器得以放鬆。
你把它寫成指標上的限定詞,例如 void add(int *restrict dst, const int *restrict a, const int *restrict b)。把 dst、a、b 標成 restrict,就是告訴編譯器它們的區域不重疊,於是它可以把每個輸入只載入一次、自由運算、寫回結果,而不必擔心透過 dst 寫入會改變 a 或 b 所指的內容。關鍵在於:restrict 是「你」的保證,而非編譯器會檢查的東西;若這些區域實際上重疊,而你又透過另一個指標存取了該物件,行為就是未定義的。
它在緊湊的數值迴圈、記憶體複製(注意 memcpy 承諾不重疊,而 memmove 不承諾)以及函式庫熱門路徑中最有用,能解鎖向量化並移除多餘的重新載入。要敬畏的危險:虛假的 restrict 承諾是未定義行為——最佳化器「被允許假設」沒有重疊,若你說謊,它可能產生無聲算出錯誤答案的程式碼,而且完全沒有任何診斷訊息。
void add(int *restrict dst, const int *restrict a, const int *restrict b, size_t n) { for (size_t i = 0; i < n; i++) dst[i] = a[i] + b[i]; } // 呼叫者必須確保 dst、a、b 指向不重疊的陣列
restrict 讓編譯器假設三個陣列不重疊,因而可移除重新載入並把迴圈向量化。
restrict 是編譯器「不會」驗證的承諾;若區域實際上重疊,結果就是未定義行為——最佳化器可能假設不重疊並無聲地產生錯誤輸出。