restrict 型別限定符 (自 C99 起)
C 型別系統 中的每個獨立型別都有該型別的幾個“限定”版本,對應於 const
、volatile
以及(對於指向物件型別的指標)restrict
限定符中的一個、兩個或全部三個。本頁描述 restrict
限定符的效果。
只有指向物件型別的指標或其(可能是多維)陣列(自 C23 起)可以被 restrict-qualified;特別是,以下是錯誤的
- int restrict *p
- float (* restrict f9)(void)
Restrict 語義僅適用於左值表示式;例如,轉換為 restrict-qualified 指標或返回 restrict-qualified 指標的函式呼叫不是左值,並且限定符沒有效果。
在宣告受限指標 P
的塊的每次執行期間(通常是 P
作為函式引數的函式體的每次執行期間),如果透過任何方式修改了可透過 P
訪問(直接或間接)的某個物件,則在該塊中對該物件的所有訪問(讀寫)都必須透過 P
(直接或間接)進行,否則行為是未定義的
void f(int n, int * restrict p, int * restrict q) { while (n-- > 0) *p++ = *q++; // none of the objects modified through *p is the same // as any of the objects read through *q // compiler free to optimize, vectorize, page map, etc. } void g(void) { extern int d[100]; f(50, d + 50, d); // OK f(50, d + 1, d); // Undefined behavior: d[1] is accessed through both p and q in f }
如果物件從未被修改,它可以透過不同的 restrict-qualified 指標進行別名訪問(請注意,如果別名 restrict-qualified 指標指向的物件本身是指標,則此別名可能會抑制最佳化)。
從一個受限指標賦值給另一個受限指標是未定義行為,除非是從外層塊中指向物件的指標賦值給內層塊中的指標(包括在呼叫具有受限指標引數的函式時使用受限指標實參)或者從函式返回時(以及在 from-pointer 的塊結束時)
int* restrict p1 = &a; int* restrict p2 = &b; p1 = p2; // undefined behavior
受限指標可以自由地賦值給非受限指標,只要編譯器能夠分析程式碼,最佳化機會仍然存在
void f(int n, float * restrict r, float * restrict s) { float *p = r, *q = s; // OK while (n-- > 0) *p++ = *q++; // almost certainly optimized just like *r++ = *s++ }
如果陣列型別使用 restrict 型別限定符(透過使用 typedef)宣告,則陣列型別不是 restrict-qualified,但其元素型別是 |
(直至 C23) |
陣列型別及其元素型別始終被認為是具有相同的 restrict-qualified |
(自 C23 起) |
typedef int *array_t[10]; restrict array_t a; // the type of a is int *restrict[10] // Notes: clang and icc reject this on the grounds that array_t is not a pointer type void *unqual_ptr = &a; // OK until C23; error since C23 // Notes: clang applies the rule in C++/C23 even in C89-C17 modes
在函式宣告中,關鍵字 restrict
可以出現在用於宣告函式引數陣列型別的方括號內。它限定了陣列型別轉換成的指標型別
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]); void g12(int n, float (*p)[n]) { f(10, n, p, p+10); // OK f(20, n, p, p+10); // possibly undefined behavior (depending on what f does) }
目錄 |
[編輯] 注意
restrict 限定符的預期用途(如 register 儲存類)是促進最佳化,並且從構成符合程式的預處理翻譯單元中刪除所有限定符例項不會改變其含義(即,可觀察行為)。
編譯器可以自由地忽略 restrict
用法的任何或所有別名含義。
為避免未定義行為,程式設計師必須確保 restrict-qualified 指標所做的別名斷言不會被違反。
許多編譯器作為語言擴充套件提供了 restrict
的反義詞:一個屬性,指示即使指標型別不同,它們也可能別名:may_alias
(gcc),
[編輯] 使用模式
有幾種常見的 restrict-qualified 指標使用模式
[編輯] 檔案作用域
檔案作用域 restrict-qualified 指標在程式執行期間必須指向單個數組物件。該陣列物件不能同時透過受限指標和透過其宣告名稱(如果有)或另一個受限指標引用。
檔案作用域受限指標可用於提供對動態分配的全域性陣列的訪問;restrict 語義使得透過此指標的引用能夠像透過其宣告名稱引用靜態陣列一樣有效地進行最佳化
float *restrict a, *restrict b; float c[100]; int init(int n) { float * t = malloc(2*n*sizeof(float)); a = t; // a refers to 1st half b = t + n; // b refers to 2nd half } // compiler can deduce from the restrict qualifiers that // there is no potential aliasing among the names a, b, and c
[編輯] 函式引數
restrict-qualified 指標最流行的用例是作為函式引數使用。
在以下示例中,編譯器可以推斷沒有修改物件的別名,從而積極最佳化迴圈。進入 f
時,受限指標 a 必須提供對其關聯陣列的獨佔訪問。特別是,在 f
中,b
和 c
都不能指向與 a
關聯的陣列,因為兩者都沒有被賦值基於 a
的指標值。對於 b
,這從其宣告中的 const-qualifier 中顯而易見,但對於 c
,需要檢查 f
的主體
float x[100]; float *c; void f(int n, float * restrict a, float * const b) { int i; for ( i=0; i<n; i++ ) a[i] = b[i] + c[i]; } void g3(void) { float d[100], e[100]; c = x; f(100, d, e); // OK f( 50, d, d+50); // OK f( 99, d+1, d); // undefined behavior c = d; f( 99, d+1, e); // undefined behavior f( 99, e, d+1); // OK }
請注意,允許 c 指向與 b 關聯的陣列。另請注意,出於這些目的,“陣列”與特定指標關聯僅表示透過該指標實際引用的陣列物件的一部分。
請注意,在上面的示例中,編譯器可以推斷 a 和 b 不會別名,因為 b 的 constness 保證它不能在函式體中依賴於 a。同樣,程式設計師可以編寫 void f(int n, float * a, float const * restrict b),在這種情況下,編譯器可以推斷透過 b 引用的物件不能被修改,因此不能同時使用 b 和 a 引用修改過的物件。如果程式設計師編寫 void f(int n, float * restrict a, float * b),編譯器將無法在不檢查函式體的情況下推斷 a 和 b 不會別名。
通常,最好在函式原型中明確地用 restrict 註釋所有非別名指標。
[編輯] 塊作用域
塊作用域 restrict-qualified 指標做出一個別名斷言,該斷言僅限於其塊。它允許僅適用於重要塊(如緊密迴圈)的區域性斷言。它還使得將接受 restrict-qualified 指標的函式轉換為宏成為可能
float x[100]; float *c; #define f3(N, A, B) \ do \ { int n = (N); \ float * restrict a = (A); \ float * const b = (B); \ int i; \ for ( i=0; i<n; i++ ) \ a[i] = b[i] + c[i]; \ } while(0)
[編輯] 結構體成員
作為結構體成員的 restrict-qualified 指標所做的別名斷言的作用域是用於訪問結構體的識別符號的作用域。
即使結構體在檔案作用域宣告,當用於訪問結構體的識別符號具有塊作用域時,結構體中的別名斷言也具有塊作用域;別名斷言僅在塊執行或函式呼叫中生效,具體取決於此結構體型別的物件是如何建立的
struct t // Restricted pointers assert that { int n; // members point to disjoint storage. float * restrict p; float * restrict q; }; void ff(struct t r, struct t s) { struct t u; // r,s,u have block scope // r.p, r.q, s.p, s.q, u.p, u.q should all point to // disjoint storage during each execution of ff. // ... }
[編輯] 關鍵字
[編輯] 示例
程式碼生成示例;使用 -S(gcc、clang 等)或 /FA(visual studio)編譯
int foo(int *a, int *b) { *a = 5; *b = 6; return *a + *b; } int rfoo(int *restrict a, int *restrict b) { *a = 5; *b = 6; return *a + *b; }
可能的輸出
; generated code on 64bit Intel platform: foo: movl $5, (%rdi) ; store 5 in *a movl $6, (%rsi) ; store 6 in *b movl (%rdi), %eax ; read back from *a in case previous store modified it addl $6, %eax ; add 6 to the value read from *a ret rfoo: movl $11, %eax ; the result is 11, a compile-time constant movl $5, (%rdi) ; store 5 in *a movl $6, (%rsi) ; store 6 in *b ret
[編輯] 參考
- C23 標準 (ISO/IEC 9899:2024)
- 6.7.3.1 restrict 的形式定義 (p: TBD)
- C17 標準 (ISO/IEC 9899:2018)
- 6.7.3.1 restrict 的形式定義 (p: 89-90)
- C11 標準 (ISO/IEC 9899:2011)
- 6.7.3.1 restrict 的形式定義 (p: 123-125)
- C99 標準 (ISO/IEC 9899:1999)
- 6.7.3.1 restrict 的形式定義 (p: 110-112)