web-dev-qa-db-ja.com

Cの「登録」キーワード?

registerキーワードはC言語で何をしますか?私はそれが最適化のために使用されていることを読みましたが、どの標準でも明確に定義されていません。それはまだ関連性があり、もしそうなら、いつそれを使用しますか?

253
Nick Van Brunt

変数が頻繁に使用されることと、可能であればプロセッサレジスタに保持することをお勧めすることは、コンパイラへのヒントです。

ほとんどの最新のコンパイラは自動的にそれを行い、人間よりもそれらを選ぶのが優れています。

321
Brian Knoblauch

コンパイラがレジスタではなくメモリに変数を保持することを決定した場合でも、レジスタ変数のアドレスを取得できないと誰も言及していないことに驚いています。

したがって、registerを使用しても何も勝ちません(とにかくコンパイラーは変数をどこに置くかを自ら決定します)&演算子を失います-使用する理由はありません。

64
qrdl

変数の保存に、RAMの代わりにCPUレジスタを使用するようにコンパイラーに指示します。レジスタはCPUにあり、RAMよりもはるかに高速にアクセスします。しかし、それはコンパイラーへの提案に過ぎず、それがうまくいかないかもしれません。

32
Andrew Barnett

私はこの質問がCに関するものであることを知っていますが、C++の同じ質問はこの質問の正確な複製として閉じられました。したがって、この答えはCには当てはまらない可能性があります。


C++ 11標準の最新ドラフト N3485 は、7.1.1/3でこれを述べています。

register指定子は、そのように宣言された変数が頻繁に使用されるという実装へのヒントです。 [note:ヒントは無視できます。ほとんどの実装では、変数のアドレスが取得された場合は無視されます。この使用は非推奨です...— end note]

C++では(ただし、Cではnot)、標準では、registerと宣言された変数のアドレスを取得できないとは規定されていません。ただし、その存続期間中にCPUレジスタに格納された変数にはメモリロケーションが関連付けられていないため、アドレスを取得しようとしても無効になり、コンパイラはregisterキーワードを無視してアドレスの取得を許可します。

20
bwDraco

オプティマイザーがこれに関してあなたができるよりも良い決定を下すので、少なくとも15年間は関係ありませんでした。関連する場合でも、SPARCやM68000などのレジスタが多数あるCPUアーキテクチャでは、レジスタが不足しているIntelの場合よりも多くの意味がありました。独自の目的のためのコンパイラ。

16
Paul Tomblin

私はそれが最適化に使用されていることを読みましたが、どの標準でも明確に定義されていません。

実際、それははC標準によって明確に定義されていますN1570ドラフト セクション6.7.1パラグラフ6を引用(他のバージョンでも同じ表現を使用):

ストレージクラス指定子registerを使用したオブジェクトの識別子の宣言は、オブジェクトへのアクセスが可能な限り高速であることを示唆しています。そのような提案が効果的である範囲は実装定義です。

単項&演算子は、registerで定義されたオブジェクトに適用できません。また、registerは外部宣言で使用できません。

registerで修飾されたオブジェクトに固有のその他の(かなり曖昧な)ルールがいくつかあります。

  • registerを使用して配列オブジェクトを定義すると、動作が未定義になります。
    修正:registerを使用して配列オブジェクトを定義することはできますが、そのようなオブジェクトでは有用なことはできません(インデックス付け配列は、初期要素のアドレスを取得する必要があります)。
  • _Alignas指定子(C11の新機能)は、そのようなオブジェクトに適用されない場合があります。
  • va_startマクロに渡されたパラメーター名がregister- qualifiedである場合、動作は未定義です。

他にもいくつかあります。規格のドラフトをダウンロードし、興味がある場合は「登録」を検索してください。

名前が示すように、registeroriginalの意味は、CPUレジスタにオブジェクトを保存することを要求することでした。しかし、コンパイラの最適化の改善により、これはあまり有用ではなくなりました。 C標準の最新バージョンでは、CPUレジスタを参照していません。CPUレジスタは、そのようなことを前提としない(必要としない)ためです(レジスタを使用しないアーキテクチャがあります)。一般的な常識は、registerをオブジェクト宣言に適用すると、生成されたコードがworsenになる可能性が高いことです。コンパイラー自身のレジスター割り当てに干渉するためです。それが有用な場合はまだいくつかあります(たとえば、変数にアクセスする頻度が本当にわかっていて、最新の最適化コンパイラが把握できる知識よりも知識が優れている場合)。

registerの主な具体的な効果は、オブジェクトのアドレスを取得しようとする試みを防ぐことです。これは、ローカル変数にのみ適用でき、最適化コンパイラは、そのようなオブジェクトのアドレスが取得されないことを自分自身で確認できるため、最適化のヒントとして特に有用ではありません。

12
Keith Thompson

実際、registerは、変数がプログラム内の他の何か(charでさえも)とエイリアスしないことをコンパイラに伝えます。

これは、さまざまな状況で最新のコンパイラーによって悪用される可能性があり、コンパイラーが複雑なコードでかなり役立つ可能性があります。単純なコードでは、コンパイラーが独自にこれを把握できます。

それ以外の場合は、目的を果たさず、レジスタの割り当てには使用されません。コンパイラーが十分に最新である限り、通常、指定してもパフォーマンスが低下することはありません。

12
Rupert

ストーリータイム!

言語としてのCは、コンピューターの抽象化です。これにより、コンピューターの機能の観点から、メモリの操作、数学の実行、物事の印刷などを行うことができます。

ただし、Cは抽象化にすぎません。そして最終的に、それがyoから抽出しているのはアセンブリ言語です。アセンブリはCPUが読み取る言語であり、それを使用する場合は、CPUの観点から物事を行います。 CPUは何をしますか?基本的に、メモリから読み取り、計算を行い、メモリに書き込みます。 CPUはメモリ内の数値を計算するだけではありません。まず、メモリからCPU内のメモリにregisterと呼ばれる番号を移動する必要があります。この番号に対して必要なことをすべて実行したら、通常のシステムメモリに戻すことができます。なぜシステムメモリを使用するのですか?レジスタの数には制限があります。最新のプロセッサでは約100バイトしか得られず、古い人気のあるプロセッサはさらに素晴らしく制限されていました(6502には3つの8ビットレジスタが無料で使用できました)。したがって、平均的な数学演算は次のようになります。

load first number from memory
load second number from memory
add the two
store answer into memory

その多くは...数学ではありません。これらのロードおよびストア操作は、処理時間の半分までかかる場合があります。 Cはコンピューターの抽象化であるため、プログラマーがレジスターを使用したりジャグリングしたりする心配をなくし、コンピューター間で数と型が異なるため、Cはレジスターの割り当てをコンパイラーだけに任せます。 1つの例外を除きます。

変数registerを宣言するとき、コンパイラーに「そう、この変数を頻繁に使用するか、短命にするか、またはその両方をするつもりです。私があなただったら、私はそれを…登録。" C標準がコンパイラが実際に何もする必要がないと言っているのは、C標準があなたがコンパイルしているコンピュータを知らないためであり、それは動作するために3つのレジスタすべてが必要な上記6502のようなものかもしれません、番号を保持するための予備のレジスタはありません。ただし、アドレスを取得できないと表示されるのは、レジスタにアドレスがないためです。彼らはプロセッサの手です。コンパイラーはアドレスを提供する必要がなく、またアドレスをまったく取得できないため、コンパイラーに対していくつかの最適化が公開されています。たとえば、常にレジスタに番号を保持できます。コンピューターのメモリのどこに保存されているかを心配する必要はありません(再度取得する必要があります)。別の変数にパンしたり、別のプロセッサに渡したり、場所を変更したりすることもできます。

tl; dr:多くの計算を行う短期間の変数。一度に宣言しすぎないでください。

7
Orion

比較のためのほんの少しのデモ(実世界の目的なし):各変数の前にregisterキーワードを削除すると、このコードはi7(GCC)で3.41秒かかりますwithregister同じコードは0.7秒で完了します。

#include <stdio.h>

int main(int argc, char** argv) {

     register int numIterations = 20000;    

     register int i=0;
     unsigned long val=0;

    for (i; i<numIterations+1; i++)
    {
        register int j=0;
        for (j;j<i;j++) 
        {
            val=j+i;
        }
    }
    printf("%d", val);
    return 0;
}
4
Robert

コンパイラの洗練されたグラフ色アルゴリズムをいじっています。これはレジスタの割り当てに使用されます。まあ、ほとんど。これはコンパイラーへのヒントとして機能します-それは本当です。ただし、レジスタ変数のアドレスを取得することは許可されていないため、全体が無視されることはありません(コンパイラーは今、あなたの慈悲に基づいて、異なる動作をしようとします)。ある意味では、それを使用しないように指示しています。

キーワードは長い間使用されていました。人差し指を使用してすべてをカウントできるレジスタが非常に少ない場合。

しかし、私が言ったように、廃止されることはあなたがそれを使用できないことを意味しません。

4
dirkgently

次のコードを使用して、QNX 6.5.0でregisterキーワードをテストしました。

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

int main(int argc, char *argv[]) {
    uint64_t cps, cycle1, cycle2, ncycles;
    double sec;
    register int a=0, b = 1, c = 3, i;

    cycle1 = ClockCycles();

    for(i = 0; i < 100000000; i++)
        a = ((a + b + c) * c) / 2;

    cycle2 = ClockCycles();
    ncycles = cycle2 - cycle1;
    printf("%lld cycles elapsed\n", ncycles);

    cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
    printf("This system has %lld cycles per second\n", cps);
    sec = (double)ncycles/cps;
    printf("The cycles in seconds is %f\n", sec);

    return EXIT_SUCCESS;
}

私は次の結果を得ました:

-> 807679611サイクルが経過しました

->このシステムには毎秒3300830000サイクルがあります

->秒のサイクルは〜0.244600です

そして今、レジスタintなしで:

int a=0, b = 1, c = 3, i;

私が得た:

-> 1421694077サイクルが経過しました

->このシステムには毎秒3300830000サイクルがあります

->秒のサイクルは〜0.430700です

4
Daniel B

70年代、C言語の最初の頃に、registerキーワードが導入され、プログラマーがコンパイラーにヒントを与え、変数が非常に頻繁に使用されること、そして、プロセッサの内部レジスタのいずれかに値を保持します。

現在、オプティマイザーは、レジスターに保持される可能性が高い変数を決定するためにプログラマーよりもはるかに効率的であり、オプティマイザーは常にプログラマーのヒントを考慮しません。

そのため、多くの人がregisterキーワードを使用しないことを誤って推奨しています。

理由を見てみましょう!

Registerキーワードには、関連する副作用があります。レジスタタイプ変数を参照(アドレスを取得)することはできません。

レジスタを使用しないように他の人に助言する人々は、これを追加の議論として誤って受け入れます。

ただし、レジスター変数のアドレスを取得できないことを知っているという単純な事実により、コンパイラー(およびそのオプティマイザー)は、ポインターを介して間接的にこの変数の値を変更できないことを知ることができます。

命令ストリームの特定のポイントで、レジスター変数の値がプロセッサーのレジスターに割り当てられており、別の変数の値を取得するためにレジスターが使用されていない場合、コンパイラーは再ロードする必要がないことを認識しますそのレジスタ内の変数の値。これにより、高価な無駄なメモリアクセスを回避できます。

独自のテストを行うと、最も内側のループのパフォーマンスが大幅に向上します。

c_register_side_effect_performance_boost

2
user5536632

レジスターは、この変数が変数の使用に使用可能な数少ないレジスターの1つでのストレージを正当化するのに十分な書き込み/読み取りができるとコーダーが信じていることをコンパイラーに通知します。通常、レジスタからの読み取り/書き込みはより高速で、より小さな命令コードセットが必要になる場合があります。

現在、ほとんどのコンパイラのオプティマイザは、その変数にレジスタを使用するかどうか、およびどのくらいの期間使用するかを判断するのに優れているため、これはあまり役に立ちません。

2
billjamesdev

Registerキーワードは、特定の変数をCPUレジスタに保存して、高速にアクセスできるようにするようコンパイラーに指示します。プログラマーの観点から、プログラムで頻繁に使用される変数にはregisterキーワードが使用されるため、コンパイラーはコードを高速化できます。 CPUレジスタまたはメインメモリに変数を保持するかどうかはコンパイラに依存しますが。

1
Sanjeev Kumar

MicrosoftのVisual C++コンパイラは、グローバルなレジスタ割り当ての最適化(/ Oeコンパイラフラグ)が有効になっている場合、registerキーワードを無視します。

MSDNの 登録キーワード を参照してください。

1
M. Dudley

サポートされているCコンパイラでは、変数の値が実際のプロセッサレジスタに保持されるようにコードを最適化しようとします。

1
Dana Holt

レジスターは、特定の変数をレジスターに、次にメモリーに格納することにより、このコードを最適化するようコンパイラーに指示します。これはコンパイラへの要求であり、コンパイラはこの要求を考慮する場合としない場合があります。変数の一部が非常に頻繁にアクセスされる場合に、この機能を使用できます。例:ループ。

もう1つは、変数をレジスタとして宣言すると、メモリに格納されていないため、そのアドレスを取得できないことです。 CPUレジスタで割り当てを取得します。

0
flying-high