Cでregisterキーワードが実際に役立つのはいつですか?


10

registerC でのキーワードの使用について混乱しています。通常、stackoverflowに関するこの質問のように、その使用は必要ないと言われています。

最新のコンパイラーのために、このキーワードはCで完全に冗長ですか、それともまだ有用な状況がありますか?はいの場合、registerキーワードの使用が実際に役立つ状況は何ですか?


4
リンクされた質問とその回答は、ここで期待できるものと同じだと思います。したがって、ここで取得できる新しい情報はありません。
Uwe Plonus 2013

@UwePlonus constキーワードについても同じだと思ったが、この質問は私が間違っていることを証明した。だから私は待って、私が何を手に入れるか見てみます。
Aseem Bansal 2013

constキーワードはレジスターとは違うものだと思います。
Uwe Plonus 2013

4
誤って時間をさかのぼり、初期のCコンパイラのいずれかを使用せざるを得ない場合に役立ちます。それ以外はまったく役に立ちませんが、何年もの間完全に時代遅れになっています。
JohnB 2013

@UwePlonusキーワードが役立つかもしれない私にとって未知のシナリオがあるかもしれないことを単に意味しました。
Aseem Bansal 2013

回答:


11

言語の点で冗長ではありません。それを使用することで、コンパイラーに変数をレジスターに保管することを「好む」と伝えます。ただし、これが実行時に実際に発生するという保証はまったくありません。


9
それ以上に、コンパイラが最もよく知っていて、あなたの息を浪費している場合がほとんどです
ダニエル・グラッツァー

6
@jozefg:さらに悪い。コンパイラがリクエスト/ヒントを尊重し、結果としてより悪いコードを生成するリスクを負います。
Bart van Ingen Schenau 2013

9

すでに述べたように、コンパイラオプティマイザは、registerエイリアスを防止する以外の目的で、キーワードを基本的に廃止します。しかし、(オフに最適化されてコンパイルされ、全体のコードベースがある-O0中でのgcc-話すが)。このようなコードの場合、registerキーワードは大きな効果があります。特に、スタックでスロットを取得する変数(つまり、すべての関数パラメーターと自動変数)registerキーワードで宣言されている場合、レジスターに直接配置できます。

実際の例を次に示します。データベースの取得が発生し、取得コードが取得したタプルをC構造体に詰め込んだと想定します。さらに、このC構造体の一部のサブセットを別の構造体にコピーする必要があると想定します。この2番目の構造体は、データベースに保存されているメタデータを表すキャッシュレコードであると考えられます。データベース内。

各構造体型へのポインターを受け取る関数が与えられ、その唯一の仕事は、いくつかのメンバーを最初の構造体から2番目の構造体にコピーすることです。構造体ポインター変数はスタック上に存在します。ある構造体のメンバーから別の構造体への割り当てが発生すると、コピーされる構造体のメンバーのアクセスを実行するために、各割り当ての構造体のアドレスがレジスターにロードされます。構造体ポインタがregisterキーワードで宣言された場合、構造体のアドレスはレジスタに残り、各割り当てのレジスタへのアドレスのロード命令を効果的に切り捨てます。

繰り返しますが、上記の説明は最適化されていないコードに適用されることに注意してください


6

基本的に、変数のアドレスを取得しないことをコンパイラーに指示すると、コンパイラーは表面上はさらに最適化を行うことができます。私の知る限り、最近のコンパイラーは、変数をレジスターに保持できるかどうかを判断する機能をかなり備えています。

例:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 

間接参照するか、アドレスを取得しますか?
2013

@detly:あなたはもちろん正しいです
ルーカス

0

16ビットコンピュータの時代には、32ビットの乗算と除算を実行するために複数のレジスタが必要になることがよくありました。浮動小数点ユニットがチップに組み込まれ、その後64ビットアーキテクチャが「引き継がれた」ため、レジスタの幅と数の両方が拡張されました。これは最終的に、CPUの完全な再設計につながります。ウィキペディアのファイルの登録を参照してください。

つまり、64ビットX86またはARMチップを使用している場合、実際に何が行われているのかを理解するには少し時間がかかります。16ビットのエンベデッドCPUを使用している場合は、実際に何かが得られるかもしれません。ただし、ほとんどの小さな組み込みチップはタイムクリティカルなものを実行しません-電子レンジが1秒に10,000回タッチパッドをサンプリングしている可能性があります-4Mhz CPUに負担をかけるものは何もありません。


1
4 MIPS / 10,000ポーリング/秒= 400命令/ポーリング。それはあなたが望むほどのマージンではありません。また、かなりの数の4 MHzプロセッサが内部でマイクロコード化されていることにも注意してください。つまり、1 MIP / MHzに近いところはありません。
John R. Strohm 2013

@ JohnR.Strohm-どれだけの命令サイクルが必要かを正確に把握できる場合もありますが、多くの場合、より安価な方法は、より高速なチップを入手して製品を入手することです。もちろん、与えられた例では、コマンドがある場合、10,000でサンプリングを続ける必要はありません。害を及ぼすことなく、1/4秒間サンプリングを再開しない場合があります。プログラマー向けの最適化が問題になる場所を特定することは、ますます困難になっています。
Meredith Poor

1
「より高速なチップを入手して製品をすぐに入手できる」とは限りません。リアルタイムの画像処理を検討してください。640x480ピクセル/フレームx 60フレーム/秒xピクセルあたりのN命令がすばやく追加されます。(リアルタイムの画像処理からの教訓は、ピクセルカーネルに血を流し、他のすべてを無視することです。ラインごとに数百回、またはパッチごとに1回、またはフレームごとに1回実行されるためです。パッチまたはフレームあたり数万回または数十万回。)
John R. Strohm 2013

@ JohnR.Strohm-リアルタイム画像処理の例をとると、最小環境は32ビットだと思います。肢に出かける(これがどれほど実用的かわからないため)チップに組み込まれている多くのグラフィックアクセラレータも画像認識に使用できる可能性があるため、レンダリングエンジンが統合されているARMチップ(たとえば)は、追加のALUを使用できる場合があります認識のため。その時までに、最適化のための 'register'キーワードの使用は問題のごく一部です。
メレディスプアー

-3

registerキーワードに何らかの意味があるかどうかを確認するために、小さなサンプルコードでは機能しません。ここに私に示唆するcコードがあります、登録キーワードはまだ意味があります。しかし、Linux上のGCCとは異なる可能性があることはわかりません。レジスタint k&lはCPUレジスタに格納されますか?Linuxユーザー(特に)は、GCCと最適化を使用してコンパイルする必要があります。Borland bcc32では、&演算子がレジスター宣言整数のエラーコードを示すため、registerキーワードは機能しているように見えます(この例では)。注意!これは、WindowsのBorlandの小さな例では当てはまりません。コンパイラーが最適化するものかどうかを実際に確認するには、それは単なる小さな例ではありません。空のループは機能しません!それでも-&演算子でアドレスを読み取ることができる場合、変数はCPUレジスタに格納されません。しかし、レジスターで宣言された変数を読み取ることができない場合(コンパイル時にエラーコードが発生します)-registerキーワードは実際に変数をCPUレジスターに配置すると想定する必要があります。さまざまなプラットフォームで異なる場合がありますが、わかりません。(それが機能する場合、「ティック」の数はレジスター宣言ではるかに少なくなります。

/* reg_or_not.c */  

#include <stdio.h>
#include <time.h>
#include <stdlib> //not requiered for Linux
#define LAPSb 50
#define LAPS 50000
#define MAXb 50
#define MAX 50000


int main (void)
{
/* 20 ints and 2 register ints */   

register int k,l;
int a,aa,b,bb,c,cc,d,dd,e,ee,f,ff,g,gg,h,hh,i,ii,j,jj;


/* measure some ticks also */  

clock_t start_1,start_2; 
clock_t finish_1,finish_2;
long tmp; //just for the workload 


/* pointer declarations of all ints */

int *ap, *aap, *bp, *bbp, *cp, *ccp, *dp, *ddp, *ep, *eep;
int *fp, *ffp, *gp, *ggp, *hp, *hhp, *ip, *iip, *jp, *jjp;
int *kp,*lp;

/* end of declarations */
/* read memory addresses, if possible - which can't be done in a CPU-register */     

ap=&a; aap=&aa; bp=&b; bbp=&bb;
cp=&c; ccp=&cc; dp=&d; ddp=&dd;
ep=&e; eep=&ee; fp=&f; ffp=&ff;
gp=&g; ggp=&gg; hp=&h; hhp=&hh;
ip=&i; iip=&ii; jp=&j; jjp=&jj;

//kp=&k;  //won't compile if k is stored in a CPU register  
//lp=&l;  //same - but try both ways !


/* what address , isn't the issue in this case - but if stored in memory    some "crazy" number will be shown, whilst CPU-registers can't be read */

printf("Address a aa: %u     %u\n",a,aa);
printf("Address b bb: %u     %u\n",b,bb);
printf("Address c cc: %u     %u\n",c,cc);
printf("Address d dd: %u     %u\n",d,dd);
printf("Address e ee: %u     %u\n",e,ee);
printf("Address f ff: %u     %u\n",f,ff);
printf("Address g gg: %u     %u\n",g,gg);
printf("Address h hh: %u     %u\n",h,hh);
printf("Address i ii: %u     %u\n",i,ii);
printf("Address j jj: %u     %u\n\n",j,jj);

//printf("Address k:  %u \n",k); //no reason to try "k" actually is in a CPU-register 
//printf("Address l:  %u \n",l); 


start_2=clock(); //just for fun      

/* to ensure workload */
for (a=1;a<LAPSb;a++) {for (aa=0;aa<MAXb;aa++);{tmp+=aa/a;}}
for (b=1;b<LAPSb;b++) {for (bb=0;bb<MAXb;bb++);{tmp+=aa/a;}}
for (a=1;c<LAPSb;c++) {for (cc=0;cc<MAXb;cc++);{tmp+=bb/b;}}
for (d=1;d<LAPSb;d++) {for (dd=0;dd<MAXb;dd++);{tmp+=cc/c;}}
for (e=1;e<LAPSb;e++) {for (ee=0;ee<MAXb;ee++);{tmp+=dd/d;}}
for (f=1;f<LAPSb;f++) {for (ff=0;ff<MAXb;ff++);{tmp+=ee/e;}}
for (g=1;g<LAPSb;g++) {for (gg=0;gg<MAXb;gg++);{tmp+=ff/f;}}
for (h=1;h<LAPSb;h++) {for (hh=0;hh<MAXb;hh++);{tmp+=hh/h;}}
for (jj=1;jj<LAPSb;jj++) {for (ii=0;ii<MAXb;ii++);{tmp+=ii/jj;}}

start_1=clock(); //see following printf
for (i=0;i<LAPS;i++) {for (j=0;j<MAX;j++);{tmp+=j/i;}} /* same double   loop - in supposed memory */
finish_1=clock(); //see following printf

printf ("Memory: %ld ticks\n\n", finish_1 - start_1); //ticks for memory

start_1=clock(); //see following printf
for (k=0;k<LAPS;k++) {for (l=0;l<MAX;l++);{tmp+=l/k;}}  /* same double       loop - in supposed register*/
finish_1=clock(); //see following printf     

printf ("Register: %ld ticks\n\n", finish_1 - start_1); //ticks for CPU register (?) any difference ?   

finish_2=clock();

printf ("Total: %ld ticks\n\n", finish_2 - start_2); //really for fun only           

system("PAUSE"); //only requiered for Windows, so the CMD-window doesn't vanish     

return 0;

} 

上記のゼロの除算があります。{tmp + = ii / jj;}を{tmp + = jj / ii;}に変更してください-これは本当に申し訳ありません
John P Eriksson

また、kとiをゼロではなく1から始めます。本当にごめんなさい。
ジョンPエリクソン

3
コメントに修正を書き込む代わりに、回答を編集できます。
Jan Doggen、2018
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.