首頁  >  文章  >  後端開發  >  C 中晦澀難懂的「restrict」關鍵字

C 中晦澀難懂的「restrict」關鍵字

WBOY
WBOY原創
2024-09-11 06:36:02405瀏覽

The Obscure “restrict” Keyword in C

介紹

除此之外,C99 添加了limit 關鍵字,作為程式設計師指定指標是指向作用域中給定物件的唯一指標的一種方式,從而給編譯器一個“提示” ”,當透過該指標存取物件時,它可能會執行額外的最佳化。

問題

為了說明限制要解決的問題,請考慮以下函數:

void update_ptrs( int *p, int *q, int const *v ) {
  *p += *v;
  *q += *v;
}

編譯器將產生 x86-64 程式碼,例如:

mov eax, [rdx]  ; tmp = *v   // 1
add [rdi], eax  ; *p += tmp
mov eax, [rdx]  ; tmp = *v   // 3
add [rsi], eax  ; *q += tmp

你可能想知道為什麼它會產生第 3 行,因為它看起來與第 1 行是多餘的。問題是編譯器不知道你沒有做這樣的事情:

int x = 1, v = 2;
update_ptrs( &v, &x, &v );   // x = 5, v = 4

在update_ptrs() 中,p 和v 會別名 相同 int,因此編譯器必須謹慎行事並假設*v 的值可以在讀取之間發生變化,因此需要額外的mov 指令。

一般來說,C 中的指標會混淆最佳化,因為編譯器無法知道兩個指標是否彼此別名。 在效能關鍵的程式碼中,消除記憶體讀取可能是一個巨大的勝利如果編譯器可以安全地做到這一點。

解決方案

為了解決上述問題,C 中添加了 limit,允許您指定給定指針是 唯一 指向該指針作用域中的對象的指針,即同一作用域別名中沒有其他指針它。

要使用限制,請將其插入聲明中的 * 和指標名稱之間。 重寫為使用限制的 update_ptrs() 將是:

void update_ptrs_v2( int *restrict p, int *restrict q,
                     int const *restrict v ) {
  *p += *v;
  *q += *v;
}

(從右到左讀取,例如 v 是指向常數 int 的受限指標;或使用 cdecl。)

透過新增限制,編譯器現在可以產生以下程式碼:

mov eax, [rdx]  ; tmp = *v
add [rdi], eax  ; *p += tmp
add [rsi], eax  ; *q += tmp

現在,編譯器能夠刪除附加 mov 指令的前第 3 行。

也許最知名的使用restrict的例子是標準函式庫函數memcpy()。 這是複製記憶體區塊最快的方法如果來源位址和目標位址重疊。當位址 重疊時,可以使用稍慢的 memmove() 函數。

陷阱

濫用限制會導致未定義的行為,例如,將 do 彼此別名的指標傳遞給 update_ptrs_v2() 或 memcpy()。 在某些情況下,編譯器可以警告您,但並非在所有情況下,因此不要依賴編譯器來捕獲誤用。

請注意,restrict 是針對給定範圍的。 將一個受限制的指標分配給同一範圍內的另一個會導致未定義的行為:

void f( int *restrict d, int *restrict s ) {
  int *restrict p = s;    // undefined behavior

但是,您可以將受限的指標指派給不受限制的指標:

void f( int *restrict d, int *restrict s ) {
  int *p = s;             // OK

即使 p 不受限制,編譯器仍然可以執行相同的最佳化。

也可以將內部作用域中的受限指標指派給外部作用域中的另一個受限指標(但反之則不然):

void f( int *restrict d, int *restrict s ) {
  {                       // inner scope
    int *restrict p = s;  // OK
    // ...
    s = p;                // undefined behavior
  }
}

何時(以及何時不)使用限制

首先,您絕對應該分析您的程式碼(甚至可能查看生成的彙編程式碼),看看使用限制是否確實能夠帶來顯著的效能改進,以證明冒潛在陷阱的風險是合理的。 診斷因濫用限製而導致的錯誤非常很難做到。

其次,如果限制的使用僅限於實現透過受限指標存取的記憶體由you分配的函數,那麼它會更安全。 例如,給定:

void safer( unsigned n ) {
  n += n % 2 != 0;  // make even by rounding up
  int *const array = malloc( n * sizeof(unsigned) );
  unsigned *restrict half_1st = array;
  unsigned *restrict half_2nd = array + n/2;
  // ...
  free( array );
}

程式碼可以安全地對陣列的前半部和後半部進行操作,因為它們不重疊(假設您從未造訪 half_1st[n/2] 或更多)。

第三,如果在函數的參數中使用restrict,那麼它可能不太安全。 例如,將 Safer() 與 update_ptrs_v2() 進行對比,其中 呼叫者 控制指標。 據所知,呼叫者錯誤並傳遞了別名的指標。

各種各樣的

只有指向物件(或void)的指標可以用restrict限定:

restrict int x;       // error: can't restrict object
int restrict *p;      // error: pointer to restrict object
int (*restrict f)();  // error: pointer-to-function

可以對結構體成員使用restrict,例如:

struct node {
   void *restrict data;
   struct node *restrict left;
   struct node *restrict right;
};

表示 data 將是指向該資料的唯一指針,而 left 和 right 永遠不會指向同一個節點。 然而,對結構成員使用限制是非常不常見的。

最後,C++ 沒有有限制。 為什麼不呢? 答案很長,但 TL;DR 版本是:

  • 它可能是 C++ 委員會不想從 C 導入的難以發現的錯誤的來源。
  • C++ 越來越多地使用指針,例如這個,使得安全使用限制變得更加困難。

但是,許多編譯器都有 __restrict__ 作為擴充。

結論

在有限的情況下,使用限制可以提高效能,但也存在一些重大缺陷。 如果您正在考慮使用限制,請先分析您的程式碼。

明智地使用。

以上是C 中晦澀難懂的「restrict」關鍵字的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn