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


273

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


42
Cでregisterキーワードは何をしますか?無視されます:)
bestsss

19
@bestsss完全に無視されません。register変数のアドレスを取得してみてください。
qrdl

4
あなたが読んでいるコードは古い youtube.com/watch?v=ibF36Yyeehw#t=1827
大佐パニック

回答:


341

変数が頻繁に使用され、可能であればプロセッサレジスタに保持することをお勧めします。

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


18
まあ、私は私のACM提出物を微調整するために登録を実験しました、そしてそれは時には本当に役に立ちました。しかし、選択を誤るとパフォーマンスが低下するので、本当に注意深くする必要があります。
ypnos 2009

81
'register'を使用しない正当な理由: 'register'と宣言された変数のアドレスを取得することはできません
Adam Rosenfield

23
一部の/多くのコンパイラは、registerキーワードを完全に無視します(これは完全に合法です)。
Euro Micelli、2009

5
ypnos:実際には、ACM ICPCの問題のソリューションの速度は、そのようなマイクロ最適化よりもアルゴリズムの選択に大きく依存します。5秒の時間制限は、特にJavaの代わりにCを使用する場合は特に、正しいソリューションには十分です。
ジョーイ

66
@ユーロ:おそらくこれはご存じでしょうが、明示的に言うと、register変数のアドレスが取得されないようにコンパイラーが必要です。これがキーワードの唯一の必須の効果ですregister。変数はこの関数内でのみ変更できると言うのは簡単になるので、これでも最適化を改善するには十分です。
デールハグルンド、2010

69

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

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


94
実際には理由があります。変数のアドレスを取得できないという単なる事実は、いくつかの最適化の機会をもたらします。コンパイラーは、変数がエイリアスされないことを証明できます。
Alexandre C.

8
コンパイラーは、重要なケースでregisterはエイリアシングが発生しないことを証明することで悪名高いので、コンパイラーがそれをレジスターに入れなくても、これに役立ちます。
Miles Rout 2014年

2
@AlexandreC、Miles、コンパイラは、&が変数のどこかで受け取られているかどうかをチェックすることで完全に問題ありません。したがって、エイリアシングの検出に関する他の困難に関係なく、何も買わないと言い直してください。K + Rが最初にCを作成したとき、次のコードを見る前に、コンパイラーが実際に宣言を見てレジスター割り振りを決定したため、&​​が使用されないことを前もって知っておく本当に役に立ちました。そのため、禁止措置が講じられています。'register'キーワードは基本的に廃止されました。
greggo 2015

25
このロジックでconstは、何も得られないので役に立たず、変数を変更する機能を失うだけです。register将来誰も考えずに変数のアドレスを取得しないようにするために使用できます。私は使う理由がありませんでしたregister
Tor Klingberg、2015年

34

変数を格納するために、RAMではなくCPUレジスタを使用するようコンパイラーに指示します。レジスタはCPU内にあり、RAMよりもアクセスが高速です。しかし、これはコンパイラへの提案にすぎず、フォロースルーされない場合があります。


8
C ++を使用している人のために追加する価値がある、C ++ではレジスタ変数のアドレスを取得できます
Will

5
@Will:...しかし、コンパイラは結果としてキーワードを無視することになるでしょう。私の答えを見てください。
bwDraco

はい、「登録」はC ++のプラセボのようですが、CコードをC ++としてコンパイルできるようにするためだけにあります。また、&varを参照またはconst-referenceで渡すことを許可している間、&varを禁止することはあまり意味がありません。
greggo 2015

22

私はこの質問がCに関するものであることを知っていますが、C ++に対する同じ質問がこの質問の完全な複製としてクローズされました。したがって、この回答はCには適用されない場合があります。


C ++ 11標準の最新のドラフトであるN3485は、7.1.1 / 3で次のように述べています。

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

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


17

オプティマイザがこれより適切な決定を行うことができるため、少なくとも15年間は関連性がありませんでした。それが関連していたとしても、SPARCやM68000のような多数のレジスタを備えたCPUアーキテクチャでは、レジスタの数が少ないIntelで行うよりもはるかに理にかなっており、そのほとんどは、独自の目的のためにコンパイラによって予約されています。


13

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

これは、さまざまな状況で最新のコンパイラーによって悪用される可能性があり、複雑なコードでコンパイラーをかなり助けることができます-単純なコードでは、コンパイラーは自分でこれを理解できます。

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


「コンパイラーに伝える..」いいえ、それはしません。あなたは、そのアドレスを取るしない限り、すべての自動変数は、そのプロパティを持っている、特定の分析可能な用途を超えた方法でそれを使用しています。したがって、コンパイラは、registerキーワードを使用するかどうかに関係なく、コードからこれを認識します。とても「REGISTER」をキーワードに、それは違法ような構築物を書くことができますことを起こるが、あなたがキーワードを使用し、ない場合はいけないような方法でアドレスを取り、その後、コンパイラはまだそれは安全です知っています。このような情報は、最適化に不可欠です。
greggo 2015

1
@greggo:registerアドレスの取得を禁止します。そうしないと、変数のアドレスが外部関数に渡されているにもかかわらず、コンパイラーがレジスターの最適化を適用できる場合があることをコンパイラーに知らせるのに役立ちます(変数はその特定の呼び出しのためにメモリにフラッシュされますが、関数が戻ると、コンパイラは再びそれをアドレスが取得されたことのない変数として扱うことができます)。
スーパーキャット2016年

@supercatコンパイラーとの会話は、まだ非常にトリッキーだと思います。それがコンパイラに伝えたいことなら、最初の変数を '&'が付いていない2番目の変数にコピーし、最初の変数を再び使用しないようにすることができます。
greggo 2016年

1
@greggo:あればということを言っbarているregister変数、コンパイラがそのレジャーでも置き換えfoo(&bar);int temp=bar; foo(&temp); bar=temp;はなく、のアドレス取ってbar他のほとんどの状況では禁止されるだろうことは、過度に複雑なルールのように見えるではないでしょう。変数をレジスタに保持できる場合は、置換によってコードが小さくなります。とにかく変数をRAMに保持する必要がある場合は、置換によってコードが大きくなります。置換をコンパイラーに任せるかどうかの問題は、どちらの場合もコードの改善につながります。
スーパーキャット2016年

1
@greggo:registerコンパイラーがアドレスの取得を許可するかどうかに関係なく、グローバル変数の修飾を許可すると、グローバル変数を使用するインライン関数がループで繰り返し呼び出される場合に、いくつかの優れた最適化が可能になります。ループの繰り返しの間、その変数をレジスターに保持する他の方法は考えられません-できますか?
スーパーキャット2016年

13

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

実際にはそれがされ明確にC標準で定義されました。N1570ドラフトセクション6.7.1パラグラフ6を引用してください(他のバージョンでも同じ文言があります):

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

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

register-qualifiedオブジェクトに固有のその他の(かなりあいまいな)ルールがいくつかあります。

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

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

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

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


それで、このプログラムの振る舞いは、C標準に従って本当に未定義ですか?C ++で明確に定義されていますか?C ++では十分に定義されていると思います。
デストラクタ

@デストラクター:なぜ未定義なのでしょうか?それがregisterあなたが考えていることなら、修飾された配列オブジェクトはありません。
キーストンプソン、

申し訳ありませんが、main()の配列宣言にregisterキーワードを書き込むのを忘れていました。C ++で明確に定義されていますか?
デストラクタ

register配列オブジェクトを定義するのは間違っていました。私の回答の更新された最初の箇条書きを参照してください。そのようなオブジェクトを定義することは合法ですが、それを使って何もすることはできません。あなたが追加した場合registerの定義にsあなたの例は、プログラムが違法である(制約違反)C. C ++での同じ制限をかけないregisterプログラムが有効なC ++になります(ただし、使用することはので、register無意味になります)。
キーストンプソン

@KeithThompson:registerキーワードは、そのような変数のアドレスを取得することが合法である場合に有用な目的に役立ちますが、アドレスが取得されたときに変数を一時変数にコピーし、一時変数から再読み込みすることによってセマンティクスに影響がない場合に限られます。次のシーケンスポイント。これにより、コンパイラーは、アドレスが取得される任意の場所でフラッシュされる場合、変数がすべてのポインターアクセスにわたってレジスターに安全に保持されると想定できます。
スーパーキャット2016

9

ストーリータイム!

言語としてのCは、コンピューターの抽象概念です。それはあなたがコンピュータをすることに関して、メモリを操作すること、数学をすること、物事を印刷することなどをすることを可能にします。

しかし、Cは単なる抽象概念です。そして最終的に、それはあなたから抽出するものはアセンブリ言語です。アセンブリは、CPUが読み取る言語であり、それを使用する場合、CPUの観点から物事を行います。CPUは何をしますか?基本的に、メモリから読み取り、計算を行い、メモリに書き込みます。CPUはメモリ内の数値を計算するだけではありません。まず、レジスタと呼ばれるCPU内のメモリからメモリに数値を移動する必要があります。この数値に必要なことをすべて実行したら、通常のシステムメモリに戻すことができます。システムメモリを使用する理由 レジスターの数は限られています。最新のプロセッサーでは約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:多くの計算を行う短期間の変数。一度に多くを宣言しないでください。


5

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

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

ただし、すでに述べたように、廃止されたからといって使用できないという意味ではありません。


13
古いハードウェアの一部は、最新のIntelマシンよりも多くのレジスタを備えていました。レジスタの数は、経過時間とは関係がなく、CPUアーキテクチャとはすべて関係があります。
ちょうど私の正しい意見

2
@JUSTMYcorrectOPINION確かに、X86は基本的に全部で6つあり、「登録」に専念するために最大で1または2を残します。実際、非常に多くのコードがレジスターの少ないマシンに記述または移植されているので、これが「レジスター」キーワードがプラセボになるのに大きく貢献したのではないかと思います。ここで4年以上が経過し、ありがたいことにx86_64が14になりました。ARMも今では大きなことです。
greggo 2015

4

比較のために(任意の実世界の目的なし)ちょっとデモ:削除するときにregister、各変数の前にキーワードを、コードのこの作品は、私のi7の(GCC)に3.41秒を要し register 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;
}

2
gcc 4.8.4と-O3では、違いはありません。-O3と40000回の反復なしでは、合計1.5秒でおそらく 50ミリ秒短縮されますが、それが統計的に有意であるかどうかを確認するために十分な時間実行しませんでした。
zstewart 2017

CLANG 5.0との違いはありません。プラットフォームはAMD64です。(ASMの出力を確認しました。)
ern0 '10年

4

次のコードを使用して、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です


2

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

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


2

70年代、C言語の冒頭にregisterキーワードが導入され、プログラマーがコンパイラーにヒントを与えることができるようになり、変数が非常に頻繁に使用されること、およびその値をプロセッサーの内部レジスターの1つに保持します。

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

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

理由を見てみましょう!

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

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

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

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

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

c_register_side_effect_performance_boost


1

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


1

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

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


1

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


0

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

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


0

最適化フラグを使用しないgcc 9.3 asm出力(この回答のすべては、最適化フラグなしの標準コンパイルを指します):

#include <stdio.h>
int main(void) {
  int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 3
        add     DWORD PTR [rbp-4], 1
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave 
        ret
#include <stdio.h>
int main(void) {
  register int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 8
        mov     ebx, 3
        add     ebx, 1
        mov     esi, ebx
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

これは、強制的ebxに計算に使用されることを意味します。つまり、呼び出し先に保存されるため、スタックにプッシュし、関数の最後に復元する必要があります。registerより多くのコード行と1つのメモリの書き込みと1つのメモリの読み取りを生成します(実際には、計算がで行われたesi場合、これは0 R / Wに最適化された可能性があり、これはC ++を使用して行われconst registerます)。使用しないregisterと、2回の書き込みと1回の読み取りが発生します(ただし、ストアからロードへの転送は読み取りで発生します)。これは、値がスタックに直接存在して更新される必要があるため、正しい値をアドレス(ポインター)で読み取ることができるためです。registerこの要件はなく、ポイントすることもできません。constそしてregister基本的に反対でvolatileあり、使用していますvolatileファイルとブロックのスコープでのconst最適化とブロックスコープでのregister最適化をオーバーライドします。const registerまたregister、constはブロックスコープでCに対して何も実行しないため、同一の出力を生成し、register最適化のみが適用されます。

clangではregister無視されますが、const最適化は引き続き行われます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.