Quand le mot-clé de registre est-il réellement utile en C?

10

Je suis confus quant à l'utilisation du registermot - clé en C. On dit généralement que son utilisation n'est pas nécessaire comme dans cette question sur stackoverflow .

Ce mot-clé est-il totalement redondant en C à cause des compilateurs modernes ou existe-t-il des situations dans lesquelles il peut encore être utile? Si oui, dans quelles situations l'utilisation d'un registermot-clé est-elle réellement utile?

Aseem Bansal
la source
4
Je pense que la question liée et les réponses à celle-ci sont les mêmes que celles auxquelles vous pouvez vous attendre ici. Il n'y aura donc pas de nouvelles informations que vous pourrez obtenir ici.
Uwe Plonus
@UwePlonus J'ai pensé la même chose du constmot-clé mais cette question a prouvé que j'avais tort. Je vais donc attendre et voir ce que j'obtiens.
Aseem Bansal
Je pense que le constmot-clé est quelque chose de différent par rapport au registre.
Uwe Plonus
4
C'est utile si vous remontez accidentellement dans le temps et que vous êtes obligé d'utiliser l'un des premiers compilateurs C. A part ça, ce n'est pas utile du tout, c'est complètement obsolète depuis des années.
JohnB
@UwePlonus Je voulais juste dire qu'il peut y avoir des scénarios inconnus dans lesquels un mot-clé pourrait être utile.
Aseem Bansal

Réponses:

11

Ce n'est pas redondant en termes de langage, c'est juste qu'en l'utilisant, vous dites au compilateur, vous "préféreriez" avoir une variable stockée dans le registre. Il n'y a cependant absolument aucune garantie que cela se produira réellement pendant l'exécution.

Jas
la source
9
Plus que cela, c'est presque toujours le cas que le compilateur connaît le mieux et vous perdez votre souffle
Daniel Gratzer
6
@jozefg: encore pire. Vous courez le risque que le compilateur honore votre demande / indice et produise un code pire en conséquence.
Bart van Ingen Schenau
9

Comme déjà mentionné, les optimiseurs de compilateur rendent essentiellement le registermot clé obsolète à des fins autres que la prévention de l'aliasing. Cependant, il existe des bases de code entières qui sont compilées avec l'optimisation désactivée ( -O0en gcc-speak ). Pour un tel code, le registermot - clé peut avoir un grand effet. Plus précisément, les variables qui autrement obtiendraient un emplacement sur la pile (c'est-à-dire tous les paramètres de fonction et les variables automatiques) peuvent être placées directement dans un registre si elles sont déclarées avec le registermot - clé.

Voici un exemple concret: supposez qu'une récupération de base de données s'est produite et que le code de récupération a placé le tuple récupéré dans une structure C. En outre, supposons qu'un certain sous-ensemble de cette structure C doit être copié dans une autre structure - peut-être que cette deuxième structure est un enregistrement de cache qui représente les métadonnées stockées dans la base de données qui, en raison de contraintes de mémoire, ne met en cache qu'un sous-ensemble de chaque enregistrement de métadonnées tel que stocké dans la base de données.

Étant donné une fonction qui prend un pointeur vers chaque type de structure et dont le seul travail consiste à copier certains membres de la structure initiale vers la deuxième structure: les variables du pointeur de structure vivront sur la pile. Comme les affectations se produisent des membres d'une structure à l'autre, les adresses de la structure seront, pour chaque affectation, chargées dans un registre afin d'effectuer l'accès des membres de la structure qui sont copiés. Si les pointeurs de structure devaient être déclarés avec le registermot clé, les adresses des structures resteraient dans les registres, supprimant effectivement les instructions de chargement d'adresse dans le registre pour chaque affectation.

Encore une fois, n'oubliez pas que la description ci-dessus s'applique au code non optimisé .

jefe2000
la source
6

Vous dites au compilateur que vous ne prendrez pas l'adresse de la variable et le compilateur peut alors ostensiblement effectuer d'autres optimisations. Pour autant que je sache, les compilateurs modernes sont assez capables de déterminer si une variable peut / doit être conservée dans un registre ou non.

Exemple:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 
Lucas
la source
Déréférencer ou prendre l'adresse de?
detly
@detly: vous avez bien sûr raison
Lucas
0

À l'époque des ordinateurs 16 bits, il fallait souvent plusieurs registres pour exécuter des multiplications et des divisions 32 bits. Au fur et à mesure que les unités à virgule flottante étaient incorporées dans les puces et que les architectures 64 bits prenaient le relais, la largeur des registres et leur nombre augmentaient. Cela conduit finalement à une ré-architecture complète du CPU. Voir Enregistrer des fichiers sur Wikipedia.

En bref, cela vous prendrait un peu de temps pour comprendre ce qui se passe réellement si vous êtes sur une puce X86 ou ARM 64 bits. Si vous utilisez un processeur intégré 16 bits, cela pourrait vous apporter quelque chose. Cependant, la plupart des petites puces intégrées ne fonctionnent pas en temps critique - votre four à micro-ondes peut échantillonner votre pavé tactile 10 000 fois par seconde - rien qui ne fatigue un processeur de 4 MHz.

Meredith Poor
la source
1
4 MIPS / 10 000 interrogations / sec = 400 instructions / interrogation. Ce n'est pas autant de marge que vous aimeriez avoir. Notez également que bon nombre de processeurs à 4 MHz ont été microcodés en interne, ce qui signifie qu'ils étaient loin de 1 MIP / MHz.
John R. Strohm
@ JohnR.Strohm - Il pourrait y avoir des situations où l'on pourrait justifier de savoir exactement combien de cycles d'instruction cela va prendre, mais souvent la solution la moins chère consiste à obtenir une puce plus rapide et à sortir le produit. Dans l'exemple donné, bien sûr, il n'est pas nécessaire de continuer à échantillonner à 10 000 si l'on a une commande - il pourrait ne pas reprendre l'échantillonnage pendant un quart de seconde sans aucun dommage. Il devient de plus en plus difficile de déterminer où l'optimisation dirigée par le programmeur va avoir de l'importance.
Meredith Poor
1
Il n'est pas toujours possible de "simplement obtenir une puce plus rapide et de sortir le produit". Pensez au traitement d'image en temps réel. 640x480 pixels / image x 60 images / seconde x N instructions par pixel s'ajoute rapidement. (La leçon du traitement d'image en temps réel est que vous transpirez du sang sur vos noyaux de pixels et que vous ignorez presque tout le reste, car il s'exécute une fois par ligne ou une fois par patch ou une fois par image, par opposition à des centaines de fois par ligne ou patch ou des dizaines ou des centaines de milliers de fois par image.)
John R. Strohm
@ JohnR.Strohm - en prenant l'exemple de traitement d'image en temps réel, je suppose que l'environnement minimum est de 32 bits. Sortir sur une branche (parce que je ne sais pas comment c'est pratique) de nombreux accélérateurs graphiques intégrés aux puces peuvent également être utilisables pour la reconnaissance d'image, donc les puces ARM (par exemple) qui ont des moteurs de rendu intégrés peuvent avoir des ALU supplémentaires utilisables pour la reconnaissance. À ce moment-là, l'utilisation du mot-clé «register» pour l'optimisation n'est qu'une infime partie du problème.
Meredith Poor
-3

Afin de déterminer si le mot clé register a une signification, de minuscules exemples de codes ne suffiront pas. Voici un code-c qui me suggère, le mot-clé de registre a toujours une signification. Mais cela pourrait être différent avec GCC sur Linux, je ne sais pas. Le registre int k & l sera-t-il stocké dans un registre CPU ou non? Les utilisateurs Linux (en particulier) devraient compiler avec GCC et l'optimisation. Avec Borland bcc32, le mot-clé register semble fonctionner (dans cet exemple), car l'opérateur & donne des codes d'erreur pour les entiers déclarés de registre. REMARQUE! Ce n'est PAS le cas d'un petit exemple avec Borland sous Windows! Pour vraiment voir ce que le compilateur optimise ou non, il doit s'agir d'un exemple plus que minuscule. Les boucles vides ne feront pas l'affaire! Néanmoins - SI une adresse PEUT être lue avec l'opérateur &, la variable n'est pas stockée dans un registre CPU. Mais si une variable déclarée de registre ne peut pas être lue (provoquant un code d'erreur lors de la compilation) - je dois présumer que le mot-clé de registre place réellement la variable dans un registre CPU. Cela peut différer sur différentes plateformes, je ne sais pas. (Si cela fonctionne, le nombre de "ticks" sera beaucoup plus faible avec la déclaration de registre.

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

} 
John P Eriksson
la source
Il y aura une division avec zéro au-dessus, veuillez changer {tmp + = ii / jj;} en {tmp + = jj / ii;} - vraiment désolé pour cela
John P Eriksson
Soit également k et i commencent par 1 - pas zéro. Vraiment désolé.
John P Eriksson
3
Vous pouvez modifier votre réponse au lieu d'écrire des corrections dans les commentaires.
Jan Doggen