Wann ist das Schlüsselwort register in C tatsächlich nützlich?

10

Ich bin verwirrt über die Verwendung des registerSchlüsselworts in C. Es wird allgemein gesagt, dass seine Verwendung nicht erforderlich ist, wie in dieser Frage zum Stapelüberlauf .

Ist dieses Schlüsselwort in C aufgrund moderner Compiler vollständig redundant oder gibt es Situationen, in denen es noch nützlich sein kann? Wenn ja, in welchen Situationen ist die Verwendung von registerSchlüsselwörtern tatsächlich hilfreich?

Aseem Bansal
quelle
4
Ich denke, die verknüpfte Frage und die Antworten darauf sind die gleichen, die Sie hier erwarten können. Es gibt also keine neuen Informationen, die Sie hier erhalten können.
Uwe Plonus
@UwePlonus Ich dachte das gleiche über constKeyword, aber diese Frage bewies, dass ich falsch lag. Also werde ich abwarten und sehen, was ich bekomme.
Aseem Bansal
Ich denke, das constSchlüsselwort ist etwas anderes als registrieren.
Uwe Plonus
4
Dies ist nützlich, wenn Sie versehentlich in die Vergangenheit reisen und gezwungen sind, einen der frühen C-Compiler zu verwenden. Abgesehen davon ist es überhaupt nicht nützlich, es ist seit Jahren völlig veraltet.
JohnB
@UwePlonus Ich meinte nur, dass es mir unbekannte Szenarien geben könnte, in denen ein Schlüsselwort nützlich sein könnte.
Aseem Bansal

Antworten:

11

Es ist sprachlich nicht redundant, es ist nur so, dass Sie dem Compiler mitteilen, dass Sie es "vorziehen" würden, eine Variable im Register zu speichern. Es gibt jedoch absolut keine Garantie dafür, dass dies tatsächlich zur Laufzeit geschieht.

Jas
quelle
9
Darüber hinaus ist es fast immer so, dass der Compiler es am besten weiß und Sie Ihren Atem verschwenden
Daniel Gratzer
6
@jozefg: noch schlimmer. Sie laufen Gefahr, dass der Compiler Ihrer Anfrage / Ihrem Hinweis nachkommt und dadurch schlechteren Code erzeugt .
Bart van Ingen Schenau
9

Wie bereits erwähnt, machen Compiler-Optimierer das registerSchlüsselwort für andere Zwecke als das Verhindern von Aliasing im Wesentlichen überflüssig. Es gibt jedoch ganze Codebasen, die bei deaktivierter Optimierung kompiliert werden ( -O0in gcc-speak ). Für einen solchen Code kann das registerSchlüsselwort eine große Wirkung haben. Insbesondere Variablen , die ansonsten einen Schlitz auf dem Stapel erhalten würden (dh alle Funktionsparameter und automatische Variablen) kann direkt in ein Register gesetzt werden , wenn mit dem erklärten registerSchlüsselwort.

Hier ein Beispiel aus der Praxis: Nehmen Sie an, dass ein Datenbankabruf stattgefunden hat und dass der Abrufcode das abgerufene Tupel in eine C-Struktur gestopft hat. Angenommen, eine Teilmenge dieser C-Struktur muss in eine andere Struktur kopiert werden. Möglicherweise handelt es sich bei dieser zweiten Struktur um einen Cache-Datensatz, der die in der Datenbank gespeicherten Metadaten darstellt, die aufgrund von Speicherbeschränkungen nur eine Teilmenge jedes Metadatensatzes als gespeichert zwischenspeichern in der Datenbank.

Bei einer Funktion, die einen Zeiger auf jeden Strukturtyp verwendet und deren einzige Aufgabe es ist, einige Elemente von der ursprünglichen Struktur in die zweite Struktur zu kopieren: Die Strukturzeigervariablen befinden sich auf dem Stapel. Wenn Zuweisungen von den Mitgliedern einer Struktur zu den Mitgliedern der anderen Struktur erfolgen, werden die Adressen der Struktur für jede Zuweisung in ein Register geladen, um den Zugriff auf die Mitglieder der Struktur durchzuführen, die kopiert werden. Wenn die Strukturzeiger mit dem registerSchlüsselwort deklariert würden, würden die Adressen der Strukturen in den Registern verbleiben, wodurch die Anweisungen zum Laden der Adresse in das Register für jede Zuweisung effektiv herausgeschnitten würden.

Denken Sie auch hier daran, dass die obige Beschreibung für nicht optimierten Code gilt.

jefe2000
quelle
6

Grundsätzlich teilen Sie dem Compiler mit, dass Sie die Adresse der Variablen nicht übernehmen und der Compiler dann angeblich weitere Optimierungen vornehmen kann. Soweit ich weiß, können moderne Compiler ziemlich gut bestimmen, ob eine Variable in einem Register gespeichert werden kann / sollte oder nicht.

Beispiel:

int main(){
        int* ptr;
        int a;
        register int b;
        ptr = &a;
        ptr = &b; //this won't compile
        return 0;
} 
Lucas
quelle
Dereferenzierung oder nehmen Sie die Adresse von?
Detly
@detly: Sie sind natürlich richtig
Lucas
0

In den 16-Bit-Computertagen benötigte man oft mehrere Register, um 32-Bit-Multiplikationen und -Divisionen auszuführen. Da Gleitkommaeinheiten in Chips eingebaut wurden und dann 64-Bit-Architekturen "übernahmen", erweiterten sich sowohl die Breite der Register als auch deren Anzahl. Dies führt schließlich zu einer vollständigen Neuarchitektur der CPU. Siehe Dateien auf Wikipedia registrieren .

Kurz gesagt, es würde einige Zeit dauern, um herauszufinden, was tatsächlich vor sich geht, wenn Sie sich auf einem 64-Bit-X86- oder ARM-Chip befinden. Wenn Sie sich auf einer eingebetteten 16-Bit-CPU befinden, erhalten Sie möglicherweise tatsächlich etwas. Die meisten kleinen eingebetteten Chips laufen jedoch nicht zeitkritisch - Ihr Mikrowellenherd tastet möglicherweise 10.000 Mal pro Sekunde Ihr Touchpad ab - nichts, was eine 4-MHz-CPU belastet.

Meredith Arm
quelle
1
4 MIPS / 10.000 Umfragen / Sek. = 400 Anweisungen / Umfrage. Das ist bei weitem nicht so viel Spielraum, wie Sie gerne hätten. Beachten Sie auch, dass einige 4-MHz-Prozessoren intern mikrocodiert wurden, was bedeutet, dass sie nicht annähernd 1 MIP / MHz waren.
John R. Strohm
@ JohnR.Strohm - Es kann Situationen geben, in denen man es rechtfertigen könnte, genau herauszufinden, wie viele Befehlszyklen es dauern wird, aber oft ist der billigere Ausweg jetzt, einfach einen schnelleren Chip zu bekommen und das Produkt aus der Tür zu holen. In dem gegebenen Beispiel muss man natürlich nicht weiter mit 10.000 abtasten, wenn man einen Befehl hat - es kann sein, dass die Abtastung für eine Viertelsekunde nicht fortgesetzt wird, ohne dass Schaden angerichtet wird. Es wird immer schwieriger herauszufinden, wo programmierergesteuerte Optimierung eine Rolle spielen wird.
Meredith Poor
1
Es ist nicht immer möglich, "nur einen schnelleren Chip zu bekommen und das Produkt aus der Tür zu holen". Betrachten Sie die Echtzeit-Bildverarbeitung. 640 x 480 Pixel / Bild x 60 Bilder / Sekunde x N Anweisungen pro Pixel werden schnell hinzugefügt. (Die Lehre aus der Echtzeit-Bildverarbeitung besteht darin, dass Sie Blut über Ihre Pixelkerne schwitzen und fast alles andere ignorieren, da es einmal pro Zeile oder einmal pro Patch oder einmal pro Frame ausgeführt wird und nicht hunderte Male pro Zeile oder Patch oder Zehntausende oder Hunderttausende Male pro Frame.)
John R. Strohm
@ JohnR.Strohm - Am Beispiel der Echtzeit-Bildverarbeitung würde ich annehmen, dass die Mindestumgebung 32 Bit beträgt. Viele Grafikbeschleuniger, die in Chips eingebaut sind, können auch für die Bilderkennung verwendet werden, sodass ARM-Chips (zum Beispiel) mit integrierten Rendering-Engines möglicherweise zusätzliche ALUs verwenden können zur Anerkennung. Zu diesem Zeitpunkt ist die Verwendung des Schlüsselworts 'register' zur Optimierung ein winziger Teil des Problems.
Meredith Poor
-3

Um festzustellen, ob das Schlüsselwort register eine Bedeutung hat, reichen winzige Beispielcodes nicht aus. Hier ist ein C-Code, der mir vorschlägt, dass das Schlüsselwort register immer noch eine Bedeutung hat. Aber es könnte bei GCC unter Linux anders sein, ich weiß es nicht. Wird das Register int k & l in einem CPU-Register gespeichert oder nicht? Linux-Benutzer sollten (insbesondere) mit GCC und Optimierung kompilieren. Bei Borland bcc32 scheint das Schlüsselwort register zu funktionieren (in diesem Beispiel), da der & -operator Fehlercodes für deklarierte Ganzzahlen des Registers angibt. HINWEIS! Dies ist bei einem winzigen Beispiel mit Borland unter Windows NICHT der Fall! Um wirklich zu sehen, was der Compiler optimiert oder nicht, muss es ein mehr als winziges Beispiel sein. Leere Schleifen reichen nicht aus! Trotzdem - WENN eine Adresse mit dem & -operator gelesen werden kann, wird die Variable nicht in einem CPU-Register gespeichert. Aber wenn eine vom Register deklarierte Variable nicht gelesen werden kann (was beim Kompilieren zu Fehlercode führt) - muss ich davon ausgehen, dass das Schlüsselwort register die Variable tatsächlich in ein CPU-Register einfügt. Es kann auf verschiedenen Plattformen unterschiedlich sein, ich weiß es nicht. (Wenn es funktioniert, ist die Anzahl der "Ticks" mit der Registerdeklaration weitaus geringer.

/* 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
quelle
Es wird eine Division mit Null oben geben, bitte ändern Sie {tmp + = ii / jj;} in {tmp + = jj / ii;} - wirklich leid
John P Eriksson
Lassen Sie auch k und i mit 1 beginnen - nicht mit Null. Tut mir sehr leid.
John P Eriksson
3
Sie können Ihre Antwort bearbeiten, anstatt Korrekturen in Kommentare zu schreiben.
Jan Doggen