14

I am confused about the use of register keyword in C. It is generally told that its use isn't needed like in this question on stackoverflow.

Is this keyword totally redundant in C due to modern compilers or are there situations in which it can still be useful? If yes, what are some situations in which use of register keyword is actually helpful?

8
  • 4
    I think the linked question and the answers to it are the same as you can expect here. So there will be no new information that you can get here.
    – Uwe Plonus
    Jul 29, 2013 at 11:20
  • @UwePlonus I thought the same about const keyword but this question proved that I was wrong. So I'll wait and see what I get. Jul 29, 2013 at 11:22
  • I think the const keyword is something different against register.
    – Uwe Plonus
    Jul 29, 2013 at 11:45
  • 4
    It's useful if you accidentally go back in time and are forced to use one of the early C compilers. Other than that it's not useful at all, it's been entirely obsolete for years.
    – JohnB
    Jul 29, 2013 at 11:55
  • @UwePlonus I just meant that there may be scenarios unknown to me in which a keyword might be useful. Jul 29, 2013 at 12:09

5 Answers 5

16

It's not redundant in terms of language, it's just that by using it, you're telling the compiler, you would "prefer" to have a variable stored in register. There is however absolutely zero guarantee that this will actually happen during runtime.

2
  • 12
    More than that, it's almost always the case that the compiler knows best and you're wasting your breath Jul 29, 2013 at 12:58
  • 7
    @jozefg: even worse. You run the risk that the compiler honours your request/hint and produces worse code as a result. Jul 29, 2013 at 18:30
13

As already mentioned, compiler optimizers essentially render the register keyword obsolete for purposes other than preventing aliasing. However, there are entire codebases which are compiled with optimization turned off (-O0 in gcc-speak). For such code, the register keyword can have great effect. Specifically, variables that would otherwise get a slot on the stack (i.e. all function parameters and automatic variables) may be placed directly into a register if declared with the register keyword.

Here's a real-world example: assume that some database retrieval has occurred and that the retrieval code has stuffed the retrieved tuple into a C struct. Further, assume that some subset of this C struct needs to be copied to another struct—maybe this second struct is a cache record that represents the metadata stored in the database that, due to memory constraints only caches a subset of each metadata record as stored in the database.

Given a function that takes a pointer to each struct type and whose sole job it is to copy some members from the initial struct to the second struct: the struct pointer variables will live on the stack. As assignments occur from one struct's members to the other's, the struct's addresses will, for each assignment, be loaded into a register in order to perform the access of the struct's members that are being copied. If the struct pointers were to be declared with the register keyword, the structs‘ addresses would remain in the registers, effectively cutting out the load-address-into-register instructions for each assignment.

Again, remember that the description above applies to unoptimized code.

2
  • What major codebases are compiled without optimization?
    – forest
    Jun 22, 2021 at 0:44
  • The codebase I work on, weighing in at about 2 million lines of C code, is compiled mostly without compiler optimization (except for a very few CPU-intensive areas of the code). It's an I/O bound application (not CPU bound), so compiler optimization doesn't do us any good. I would imagine that other I/O bound applications might be in a similar situation.
    – jefe2000
    Jun 24, 2021 at 0:12
7

You basically tell the compiler that you won't take the address of the variable and the compiler can then ostensibly make further optimizations. As far as I know, modern compilers are pretty capable to determine if a variable can/should be kept in a register or not.

Example:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 
2
  • Dereference or take the address of?
    – detly
    Jul 29, 2013 at 12:18
  • @detly: you are of course correct
    – Lucas
    Jul 29, 2013 at 12:23
1

In the 16-bit computer days, one often needed multiple registers to execute 32-bit multiplies and divides. As floating point units were incorporated into chips and then 64-bit architectures 'took over', both the width of the registers and the number of them expanded. This eventually leads to a complete re-architecting of the CPU. See Register Files on Wikipedia.

In short, it would take you a bit of time to figure out what is actually going on if you're on a 64-bit X86 or ARM chip. If you're on a 16-bit embedded CPU, this might actually get you something. However, most small embedded chips don't run anything time critical - your microwave oven might be sampling your touchpad 10,000 times a second - nothing that strains a 4Mhz CPU.

4
  • 1
    4 MIPS / 10,000 polls/sec = 400 instructions/poll. That's not nearly as much margin as you'd like to have. Also note that quite a few 4 MHz processors were microcoded internally, meaning that they were nowhere near 1 MIP/MHz. Jul 29, 2013 at 21:31
  • @JohnR.Strohm - There might be situations where one could justify figuring out exactly how many instruction cycles it's going to take, but often the cheaper way out now is to just get a faster chip and get the product out the door. In the example given, of course, one doesn't have to continue to sample at 10,000 if one has a command - it might not resume sampling for a quarter of a second with no harm done. It is becoming increasingly difficult to figure out where programmer directed optimization is going to matter. Jul 29, 2013 at 23:22
  • 1
    It is not always possible to "just get a faster chip and get the product out the door". Consider real-time image processing. 640x480 pixels/frame x 60 frames/second x N instructions per pixel adds quickly. (The lesson from real-time image processing is that you sweat blood over your pixel kernels and you just about ignore everything else, because it runs once per line or once per patch or once per frame, as opposed to hundreds of times per line or patch or tens or hundreds of thousands of times per frame.) Jul 31, 2013 at 11:02
  • @JohnR.Strohm - taking the real-time image processing example, I would presume the minimum environment is 32 bits. Going out on a limb (because I don't know how practical this is to do) many graphics accelerators built into chips might also be usable for image recognition, so ARM chips (for example) that have integrated rendering engines may have additional ALUs usable for recognition. By that time the use of the 'register' keyword for optimization is a tiny part of the problem. Jul 31, 2013 at 17:55
-3

In order to establish whether the register keyword has any significanse, tiny example codes won't do. Here is a c-code which suggests to me, the register keyword still has a significance. But it might be different with GCC on Linux, I don't know. WILL register int k & l be stored in a CPU register or not ? Linux users (especially) should compile with GCC and optimization. With Borland bcc32 the register keyword appears to function (in this example), as the &-operator gives error codes for register declared integers. NOTE! This is NOT the case with a tiny example with Borland on Windows ! In order to really see what the compiler optimizes or not, it has to be a more than a tiny example. Empty loops won't do ! Nevertheless - IF an address CAN be read with the &-operator, the variable isn't stored in a CPU register. But if a register declared variable can't be read (causing error code at compilation) - I have to presume that the register keyword actually do put the variable in a CPU-register. It might differ on various platforms, I don't know. (If it works , the number of "ticks" will be far lower with the register declaration.

/* 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;

} 
3
  • There will be a division with zero above, please change {tmp+=ii/jj;} to {tmp+=jj/ii;} - truly sorry for this Feb 14, 2018 at 4:59
  • Also let k and i begin with 1 - not zero. Very sorry. Feb 14, 2018 at 5:10
  • 3
    You can edit your answer instead of writing corrections in comments.
    – Jan Doggen
    Feb 14, 2018 at 7:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.