C ++でrestrictキーワードはどういう意味ですか?


182

C ++では、restrictキーワードはどういう意味ですか?

関数に指定された2つ以上のポインターが重複しないことを意味しますか?他に何を意味しますか?


23
restrictc99キーワードです。はい、Rpbert S. Barnesさん、ほとんどのコンパイラがをサポートしていることを知ってい__restrict__ます。アンダースコアが2つあるものはすべて、定義上、実装固有であり、したがってC ++ではなく、コンパイラ固有のバージョンであることに注意してください。
KitsuneYMG 2010年

5
何?それが実装固有であるからといって、それがC ++にならないわけではありません。C ++は、実装固有のものを明示的に許可し、それを禁止したり、C ++にレンダリングしたりしません。
アリス

4
@Alice KitsuneYMGは、ISO C ++の一部ではなく、代わりにC ++拡張と見なされることを意味します。コンパイラの作成者は、ISO C ++と共存し、通常は移植性のない、または移植性のない非公式なC ++の追加の一部として機能する、独自の拡張機能を作成および配布できます。例としては、MSの古いManaged C ++と、より最近のC ++ / CLIがあります。その他の例としては、共通#warningディレクティブなどの一部のコンパイラーによって提供されるプリプロセッサーディレクティブやマクロ、または関数のシグニチャーマクロ(__PRETTY_FUNCTION__GCC、__FUNCSIG__MSVCなど)があります。
ジャスティン時間-モニカ

4
@Alice私の知る限り、C ++ 11はすべてのC99の完全なサポートを義務付けておらず、C ++ 14やC ++ 17について私が知っていることもしていません。 restrictC ++キーワード(en.cppreference.com/w/cpp/keywordを参照)とは見なされず、実際、restrictC ++ 11標準で言及されている唯一のもの(open-std.org/jtc1/sc22/wg21を参照) /docs/papers/2012/n3337.pdf:、軽微な編集上の変更、§17.2[library.c]、PDFのページ413)の状態とFDISのコピーこと
復活モニカ-ジャスティン時間

4
@アリスどうやって?私はそれが言う部分明記restrictすることであるから省略し、これらの機能は、C ++標準ライブラリに含まれているとき、C標準ライブラリ関数のシグネチャとセマンティクス(のうち左から除外します)。または、言い換えると、C標準ライブラリ関数のシグネチャがrestrictCに含まれている場合、restrictキーワードをC ++の同等のシグネチャから削除する必要があるという事実を述べました。
ジャスティン時間-2016

回答:


143

Christer Ericsonは、彼の論文「Memory Optimization」でrestrictまだC ++標準の一部ではありませんが、多くのコンパイラーでサポートされており、可能な場合は使用することをお勧めしています。

キーワードを制限する

!1999 ANSI / ISO C標準の新機能

!C ++標準にはまだ含まれていませんが、多くのC ++コンパイラでサポートされています

!ヒントのみなので、何もせず、引き続き適合している可能性があります

制限付きのポインタ(または参照)...

!...基本的に、コンパイラーは、ポインターのスコープについて、ポインターのターゲットにはそのポインター(およびそこからコピーされたポインター)を介してのみアクセスすることを約束します。

それをサポートするC ++コンパイラでは、Cと同じように動作するはずです。

詳細については、このSO投稿を参照してください 。C99の「restrict」キーワードの実際の使用法

エリクソンの論文にざっと目を通すのに30分かかります。それは興味深く、一見の価値があります。

編集する

また、IBMのAIX C / C ++コンパイラが__restrict__キーワードをサポートしていることもわかりました。

次のプログラムはg ++で正常にコンパイルされるため、g ++もこれをサポートしているようです。

#include <stdio.h>

int foo(int * __restrict__ a, int * __restrict__ b) {
    return *a + *b;
}

int main(void) {
    int a = 1, b = 1, c;

    c = foo(&a, &b);

    printf("c == %d\n", c);

    return 0;
}

私はまた、の使用に関する素晴らしい記事を見つけましたrestrict

制限キーワードの謎を解く

編集2

C ++プログラムでの制限の使用について具体的に説明している記事に出くわしました。

Load-hit-storesと__restrictキーワード

また、Microsoft Visual C ++ __restrictキーワードをサポートしています。


2
メモリー最適化のペーパーリンクは機能していません。これが彼のGDCプレゼンテーションの音声へのリンクです。gdcvault.com/play/1022689/Memory
Grimeh

1
@EnnMichael:もちろん、移植可能なC ++プロジェクトで使用する場合は#ifndef __GNUC__ #define __restrict__ /* no-op */、同様の方法を使用してください。そして、それが定義されている__restrict場合にそれを_MSC_VER定義します。
Peter Cordes

96

他の人が言ったように、ifはC ++ 14の時点で何も意味しないので__restrict__、C99と同じように機能するGCC拡張を考えてみましょうrestrict

C99

restrict2つのポインタが重複するメモリ領域を指すことはできないと述べています。最も一般的な使用法は関数の引数です。

これにより、関数の呼び出し方法が制限されますが、より多くのコンパイル最適化が可能になります。

呼び出し元がrestrict契約に従わない場合、動作は未定義です。

C99 N1256ドラフト 6.7.3 / 7「タイプ修飾子」と言います。

制限修飾子(レジスタストレージクラスなど)の使用目的は、最適化を促進することであり、適合プログラムを構成するすべての前処理翻訳単位から修飾子のすべてのインスタンスを削除しても、その意味(つまり、観察可能な動作)は変わりません。

6.7.3.1「制限の正式な定義」では、詳細について説明しています。

可能な最適化

Wikipediaの例では、され、非常照明します。

これは、1つのアセンブリ命令を保存する方法を明確に示しています

制限なし:

void f(int *a, int *b, int *x) {
  *a += *x;
  *b += *x;
}

疑似アセンブリ:

load R1  *x    ; Load the value of x pointer
load R2  *a    ; Load the value of a pointer
add R2 += R1    ; Perform Addition
set R2  *a     ; Update the value of a pointer
; Similarly for b, note that x is loaded twice,
; because x may point to a (a aliased by x) thus 
; the value of x will change when the value of a
; changes.
load R1  *x
load R2  *b
add R2 += R1
set R2  *b

制限あり:

void fr(int *restrict a, int *restrict b, int *restrict x);

疑似アセンブリ:

load R1  *x
load R2  *a
add R2 += R1
set R2  *a
; Note that x is not reloaded,
; because the compiler knows it is unchanged
; "load R1 ← *x" is no longer needed.
load R2  *b
add R2 += R1
set R2  *b

GCCは本当にそれをしますか?

g++ 4.8 Linux x86-64:

g++ -g -std=gnu++98 -O0 -c main.cpp
objdump -S main.o

-O0、それらは同じです。

-O3

void f(int *a, int *b, int *x) {
    *a += *x;
   0:   8b 02                   mov    (%rdx),%eax
   2:   01 07                   add    %eax,(%rdi)
    *b += *x;
   4:   8b 02                   mov    (%rdx),%eax
   6:   01 06                   add    %eax,(%rsi)  

void fr(int *__restrict__ a, int *__restrict__ b, int *__restrict__ x) {
    *a += *x;
  10:   8b 02                   mov    (%rdx),%eax
  12:   01 07                   add    %eax,(%rdi)
    *b += *x;
  14:   01 06                   add    %eax,(%rsi) 

初心者の場合、呼び出し規約は次のとおりです。

  • rdi =最初のパラメータ
  • rsi = 2番目のパラメーター
  • rdx = 3番目のパラメーター

GCCの出力はwikiの記事よりもさらに明確でした:4つの指示対3つの指示。

配列

ここまでは、単一の命令貯蓄を持っていますが、ポインタがオーバーループする配列を表す場合で述べたように、一般的なユースケースは、その後、命令の束が、保存することができsupercatマイケル

例を考えてみましょう:

void f(char *restrict p1, char *restrict p2, size_t size) {
     for (size_t i = 0; i < size; i++) {
         p1[i] = 4;
         p2[i] = 9;
     }
 }

のためrestrict、スマートコンパイラ(または人間)はそれを次のように最適化できます。

memset(p1, 4, size);
memset(p2, 9, size);

まともなlibc実装(glibcなど)でアセンブリ最適化される可能性があるため、潜在的にはるかに効率的です。パフォーマンスの観点から、std :: memcpy()またはstd :: copy()を使用する方が良いですか?おそらくSIMD命令で

制限なしでは、この最適化は実行できません。たとえば、次のことを考慮してください。

char p1[4];
char *p2 = &p1[1];
f(p1, p2, 3);

次にforバージョンは次のようになります:

p1 == {4, 4, 4, 9}

しばらくmemsetバージョンが作ります:

p1 == {4, 9, 9, 9}

GCCは本当にそれをしますか?

GCC 5.2.1.Linux x86-64 Ubuntu 15.10:

gcc -g -std=c99 -O0 -c main.c
objdump -dr main.o

では-O0、どちらも同じです。

-O3

  • 制限付き:

    3f0:   48 85 d2                test   %rdx,%rdx
    3f3:   74 33                   je     428 <fr+0x38>
    3f5:   55                      push   %rbp
    3f6:   53                      push   %rbx
    3f7:   48 89 f5                mov    %rsi,%rbp
    3fa:   be 04 00 00 00          mov    $0x4,%esi
    3ff:   48 89 d3                mov    %rdx,%rbx
    402:   48 83 ec 08             sub    $0x8,%rsp
    406:   e8 00 00 00 00          callq  40b <fr+0x1b>
                            407: R_X86_64_PC32      memset-0x4
    40b:   48 83 c4 08             add    $0x8,%rsp
    40f:   48 89 da                mov    %rbx,%rdx
    412:   48 89 ef                mov    %rbp,%rdi
    415:   5b                      pop    %rbx
    416:   5d                      pop    %rbp
    417:   be 09 00 00 00          mov    $0x9,%esi
    41c:   e9 00 00 00 00          jmpq   421 <fr+0x31>
                            41d: R_X86_64_PC32      memset-0x4
    421:   0f 1f 80 00 00 00 00    nopl   0x0(%rax)
    428:   f3 c3                   repz retq

    memset予想通り2つの呼び出し。

  • 制限なし:stdlib呼び出しなし、ここで再現するつもりはない16回の反復幅のループ展開のみ :-)

私はそれらをベンチマークする忍耐力がありませんでしたが、制限バージョンの方が速くなると思います。

厳格なエイリアシングルール

restrictキーワードは、互換性のあるタイプのポインタ(例えば、2に影響を与えint*、厳密なエイリアシング規則は互換性のない型をエイリアシングすると、デフォルトでは未定義の動作であることを言うので)、およびコンパイラが想定できるので、それが離れて起こると最適化しません。

参照:厳密なエイリアスルールとは何ですか?

参考資料として機能しますか?

GCCのドキュメントによると、次のようになっています:https : //gcc.gnu.org/onlinedocs/gcc-5.1.0/gcc/Restricted-Pointers.html構文:

int &__restrict__ rref

thisメンバー関数のバージョンもあります:

void T::fn () __restrict__

素敵な回答者。厳密なエイリアスが無効-fno-strict-aliasingになっている場合restrict、同じ型のポインタと異なる型のポインタの間に違いはないはずですか?(私は「restrictキーワードは互換性のある型のポインターにのみ影響する」と言っています)
idclev 463035818

@ tobi303わからない!あなたが確かに見つけたら私に知らせてください;-)
Ciro Santilli郝海东冠状病六四事件法轮功

@jwwはい、それはフレージングのより良い方法です。更新しました。
Ciro Santilli郝海东冠状病六四事件法轮功

restrictC ++では何かを意味します。restrictC ++プログラムからのパラメーターを使用してCライブラリー関数を呼び出す場合は、その意味を理解する必要があります。基本的restrictに、CライブラリAPIで使用されている場合、それはLispの動的FFIを含む、任意の言語からそれを呼び出す人にとって何かを意味します。
Kaz

22

何もない。C99標準に追加されました。


8
それは完全に真実ではありません。どうやらそれは一部のC ++コンパイラーでサポートされているようで、一部の人々はそれが利用可能であるときにそれを使用することを強くお勧めします。以下の私の回答を参照してください。
ロバートS.バーンズ

18
@Robert S Barnes:C ++標準ではrestrictキーワードとして認識されません。したがって、私の答えは正しいです。あなたが説明するのは実装固有の振る舞いであり、あなた本当に依存すべきではないものです。
2009

27
@dirkgently:すべての敬意を払って、なぜですか?多くのプロジェクトは、特定のコンパイラーまたはごく少数のコンパイラーのみでサポートされる特定の非標準言語拡張に関連付けられています。Linuxカーネルとgccが思い浮かびます。プロジェクトの有効期間全体にわたって特定のコンパイラー、または特定のコンパイラーの特定のリビジョンを使用することも珍しくありません。すべてのプログラムが厳密に準拠している必要はありません。
ロバートS.バーンズ

7
@Rpbert S. Barnes:実装固有の動作に依存すべきではない理由をこれ以上強調することはできません。Linuxとgccについては-考えてみれば、なぜそれらが防御の良い例ではないのかがわかるでしょう。中程度に成功したソフトウェアでさえ、その存続期間中、単一のコンパイラバージョンで実行されることはまだありません。
2009

16
@Rpbert S. Barnes:質問はc ++と述べた。MSVCではなく、gccではなく、AIXではありません。acidzombie24がコンパイラ固有の拡張機能を必要とする場合、彼はそのように言ったりタグを付けたりすべきでした。
KitsuneYMG 2010年

12

これは、このキーワードを追加するための最初の提案です。しかし、ひどく指摘したように、これはC99の機能です。C ++とは関係ありません。


5
多くのC ++コンパイラ__restrict__は、私の知る限り、同じキーワードをサポートしています。
Robert S. Barnes

C ++プログラムはCライブラリを呼び出し、Cライブラリはを使用するため、C ++とはすべて関係がありますrestrict。C ++プログラムの動作は、によって示される制限に違反すると、未定義になりrestrictます。
Kaz

@kaz完全に間違っています。C ++のキーワードや機能ではないため、C ++とは関係ありません。C++でCヘッダーファイルを使用する場合は、restrictキーワードを削除する必要があります。もちろん、それらを制限付きとして宣言するC関数にエイリアスポインターを渡すと(C ++またはCから実行できます)、それは未定義ですが、それはあなた次第です。
ジムバルター

@JimBalterわかりました。つまり、C ++プログラムはCライブラリを呼び出し、Cライブラリはを使用するということrestrictです。C ++プログラムの動作は、restrictによって暗示される制限に違反すると未定義になります。しかし、これは実際にはC ++とは関係ありません。
Kaz

5

C ++にはそのようなキーワードはありません。C ++キーワードのリストは、C ++言語標準のセクション2.11 / 1にあります。restrictC言語のC99バージョンのキーワードであり、C ++ではありません。


5
多くのC ++コンパイラ__restrict__は、私の知る限り、同じキーワードをサポートしています。
Robert S. Barnes

18
@Robert:しかし、C ++にはそのようなキーワードはありません。個々のコンパイラが行うのは独自のビジネスですが、C ++言語の一部ではありません。
2009

4

一部のCライブラリのヘッダーファイルはキーワードを使用するため、C ++言語はそれに対して何らかの処理を行う必要があります。最低限、キーワードを無視するため、#defineでキーワードを空白のマクロに定義してキーワードを抑制する必要はありません。 。


3
これはextern C、AIX C / C ++コンパイラーが__rerstrict__キーワードを処理する場合のように、宣言を使用して処理されるか、暗黙的にドロップされることによって処理されると思います。このキーワードはgccでもサポートされているため、コードはg ++でも同じようにコンパイルされます。
ロバートS.バーンズ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.