什么时候register关键字在C中真正有用?


10

我对register在C中使用关键字感到困惑。通常会告诉人们,不需要像在stackoverflow这个问题中那样使用它。

由于现代编译器,此关键字在C中是完全多余的吗?还是在某些情况下它仍然有用?如果是,在哪些情况下使用register关键字实际上会有所帮助?


4
我认为链接的问题及其答案与您在此处所期望的相同。因此,这里没有新的信息。
Uwe Plonus

@UwePlonus我对const关键字也有同样的想法,但是这个问题证明我错了。因此,我将拭目以待。
Aseem Bansal

我认为const关键字与register有所不同。
Uwe Plonus

4
如果您不小心回到了过去并被迫使用早期的C编译器之一,这将非常有用。除此之外,它根本没有用,多年来已经完全过时了。
JohnB

@UwePlonus我只是说,在某些情况下我可能不知道使用关键字可能有用的情况。
Aseem Bansal

Answers:


11

就语言而言,这不是多余的,只是通过使用它,您告诉编译器,您“倾向于”将变量存储在寄存器中。但是,绝对没有零保证,这将在运行时实际发生。


9
不仅如此,编译器最
擅长的

6
@jozefg:甚至更糟。您冒着编译器尊重您的请求/提示并因此产生更差代码的风险。
巴特·范·英根·谢瑙

9

如前所述,编译器优化程序实际上使register关键字已过时,而不是防止混叠。但是,有整个代码库都是-O0在关闭优化的情况下编译的(在gcc-speak中)。对于这样的代码,register关键字可以产生很大的效果。具体而言,如果使用关键字声明,则原本会在堆栈上占用插槽的变量(即所有函数参数和自动变量)可以直接放置到寄存器中register

这是一个真实的示例:假设发生了一些数据库检索,并且检索代码已将检索到的元组填充到C结构中。此外,假设需要将此C结构的某些子集复制到另一个结构-也许第二个结构是表示存储在数据库中的元数据的缓存记录,由于内存限制,该元数据仅缓存所存储的每个元数据记录的子集在数据库中。

给定一个获取指向每种结构类型的指针的函数,其唯一的工作就是将某些成员从初始结构复制到第二个结构:结构指针变量将存在于堆栈中。当从一个结构的成员到另一结构的成员进行分配时,对于每个分配,该结构的地址将被加载到寄存器中,以执行对正在复制的结构的成员的访问。如果要使用register关键字声明结构指针,则结构的地址将保留在寄存器中,从而为每个分配有效地切出了“将地址装入寄存器”指令。

同样,请记住,上面的描述适用于未优化的代码。


6

基本上,您告诉编译器您不会使用变量的地址,然后表面上编译器可以进行进一步的优化。据我所知,现代编译器非常有能力确定是否可以/应该将变量保存在寄存器中。

例:

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

取消引用还是取地址?
2013年

@detly:您当然是正确的
Lucas

0

在16位计算机时代,经常需要多个寄存器来执行32位乘法和除法。随着浮点单元被集成到芯片中,然后“ 64位”架构被“接管”,寄存器的宽度和数量都在增加。这最终导致对CPU的完整重新架构。请参阅在Wikipedia上注册文件

简而言之,如果您使用的是64位X86或ARM芯片,则需要花费一些时间才能弄清楚实际情况。如果您使用的是16位嵌入式CPU,则实际上可能会让您有所收获。但是,大多数小型嵌入式芯片对时间的要求并不严格-微波炉可能每秒对触摸板进行采样10,000次-不会给4Mhz CPU造成压力。


1
4 MIPS / 10,000次轮询/秒= 400条指令/轮询 这几乎不是您想要的利润。还要注意,相当多的4 MHz处理器在内部进行了微编码,这意味着它们远未达到1 MIP / MHz。
约翰·R·斯特罗姆

@ JohnR.Strohm-可能在某些情况下,人们可以弄清楚到底要花多少个指令周期是合理的,但是现在通常更便宜的方法是获得更快的芯片并将产品推向市场。当然,在给出的示例中,如果有命令,则不必继续以10,000进行采样-它可能不会在没有造成任何损害的情况下恢复四分之一秒的采样。弄清程序员指导的优化在哪里变得越来越困难。
Meredith Poor

1
并非总是能够“仅获得更快的芯片并将产品送出大门”。考虑实时图像处理。640x480像素/帧x 60帧/秒x每像素N条指令会快速添加。(从实时图像处理中学到的教训是,您在像素内核上流血,几乎忽略了其他所有内容,因为它每行运行一次,每个补丁运行一次或每帧运行一次,而不是每行运行数百次或补丁或每帧成千上万次。)
John R. Strohm

@ JohnR.Strohm-以实时图像处理示例为例,我假设最小环境为32位。一路走出去(因为我不知道这样做有多实用),内置在芯片中的许多图形加速器也可能可用于图像识别,因此,集成了渲染引擎的ARM芯片(例如)可能还有其他可用的ALU。获得认可。到那时,使用'register'关键字进行优化只是问题的一小部分。
Meredith Poor

-3

为了确定register关键字是否有意义,微小的示例代码将不起作用。这是一个C代码,向我建议,register关键字仍然有意义。但我不知道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 '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.