Kiedy słowo kluczowe register jest rzeczywiście przydatne w C?


10

Jestem zdezorientowany co do użycia registersłowa kluczowego w C. Powszechnie mówi się, że jego użycie nie jest potrzebne, jak w tym pytaniu o przepełnieniu stosu .

Czy to słowo kluczowe jest całkowicie zbędne w C ze względu na nowoczesne kompilatory, czy też są sytuacje, w których może być nadal przydatne? Jeśli tak, w jakich sytuacjach użycie registersłowa kluczowego jest rzeczywiście pomocne?


4
Myślę, że powiązane pytanie i odpowiedzi na nie są takie same, jak można się tutaj spodziewać. Więc nie będzie żadnych nowych informacji, które można tu uzyskać.
Uwe Plonus

@UwePlonus Tak samo myślałem o constsłowie kluczowym, ale to pytanie udowodniło, że się myliłem. Więc poczekam i zobaczę, co dostanę.
Aseem Bansal

Myślę, że constsłowo kluczowe różni się od rejestru.
Uwe Plonus

4
Jest to przydatne, jeśli przypadkowo cofniesz się w czasie i będziesz zmuszony użyć jednego z wczesnych kompilatorów C. Poza tym nie jest w ogóle przydatny, przez lata był całkowicie przestarzały.
JohnB

@UwePlonus Chciałem tylko powiedzieć, że mogą istnieć nieznane mi scenariusze, w których słowo kluczowe może być przydatne.
Aseem Bansal

Odpowiedzi:


11

Nie jest zbędny pod względem językowym, po prostu używając go, mówisz kompilatorowi, „wolałbyś” mieć zmienną zapisaną w rejestrze. Nie ma jednak absolutnie zerowej gwarancji, że tak się stanie podczas działania.


9
Co więcej, prawie zawsze jest tak, że kompilator wie najlepiej, a ty marnujesz oddech
Daniel Gratzer

6
@ jozefg: jeszcze gorzej. Ryzykujesz, że kompilator zaakceptuje twoje żądanie / podpowiedź i w rezultacie wygeneruje gorszy kod.
Bart van Ingen Schenau

9

Jak już wspomniano, optymalizatory kompilatora powodują, że registersłowo kluczowe staje się przestarzałe z powodów innych niż zapobieganie aliasingowi. Istnieją jednak całe bazy kodu, które są kompilowane przy wyłączonej optymalizacji ( -O0w gcc-speak ). W przypadku takiego kodu registersłowo kluczowe może mieć świetny efekt. W szczególności zmienne, które w przeciwnym razie otrzymałyby miejsce na stosie (tj. Wszystkie parametry funkcji i zmienne automatyczne) mogą zostać umieszczone bezpośrednio w rejestrze, jeśli zostaną zadeklarowane za pomocą registersłowa kluczowego.

Oto przykład ze świata rzeczywistego: załóżmy, że miało miejsce pewne pobieranie bazy danych i że kod pobierania umieścił odzyskaną krotkę w strukturze C. Ponadto załóżmy, że pewien podzbiór tej struktury C musi zostać skopiowany do innej struktury - być może ta druga struktura jest rekordem pamięci podręcznej, który reprezentuje metadane przechowywane w bazie danych, które z powodu ograniczeń pamięci buforują tylko podzbiór każdego rekordu metadanych jako przechowywane w bazie danych.

Biorąc pod uwagę funkcję, która pobiera wskaźnik do każdego typu struktury i której jedynym zadaniem jest skopiowanie niektórych elementów z początkowej struktury do drugiej struktury: zmienne wskaźnika struktury będą istnieć na stosie. Gdy przyporządkowania następują od członków jednej struktury do pozostałych, adresy struktury będą, dla każdego przypisania, ładowane do rejestru w celu uzyskania dostępu do kopiowanych elementów struktury. Jeśli wskaźniki struktury miałyby zostać zadeklarowane za pomocą registersłowa kluczowego, adresy struktur pozostałyby w rejestrach, skutecznie wycinając instrukcje ładowania adresu do rejestru dla każdego przypisania.

Ponownie pamiętaj, że powyższy opis dotyczy niezoptymalizowanego kodu.


6

Zasadniczo mówisz kompilatorowi, że nie weźmiesz adresu zmiennej, a następnie kompilator może pozornie dokonywać dalszych optymalizacji. O ile mi wiadomo, nowoczesne kompilatory są w stanie określić, czy zmienna może / powinna być przechowywana w rejestrze, czy nie.

Przykład:

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

Dereferencja lub adres?
detly

@detly: oczywiście masz rację
Lucas,

0

W 16-bitowych dniach komputerowych często potrzebowano wielu rejestrów, aby wykonać 32-bitowe mnożenia i dzielenia. Ponieważ jednostki zmiennoprzecinkowe zostały włączone do układów scalonych, a następnie „przejęły” je architektury 64-bitowe, zarówno szerokość rejestrów, jak i ich liczba wzrosły. To ostatecznie prowadzi do całkowitej przebudowy procesora. Zobacz Rejestracja plików na Wikipedii.

Krótko mówiąc, zajęłoby ci trochę czasu, aby dowiedzieć się, co się właściwie dzieje, jeśli masz 64-bitowy układ X86 lub ARM. Jeśli korzystasz z 16-bitowego wbudowanego procesora, może coś ci w tym pomóc. Jednak większość małych wbudowanych układów nie działa krytycznie - kuchenka mikrofalowa może próbować touchpad 10 000 razy na sekundę - nic, co nie obciąża procesora 4 MHz.


1
4 MIPS / 10000 ankiet / s = 400 instrukcji / ankieta. To nie jest prawie tak duży margines, jak byś chciał. Zauważ też, że sporo procesorów 4 MHz zostało wewnętrznie mikrokodowanych, co oznacza, że ​​nie były one zbliżone do 1 MIP / MHz.
John R. Strohm,

@ JohnR.Strohm - Mogą zdarzyć się sytuacje, w których można uzasadnić dokładne określenie liczby cykli instrukcji, ale często tańszym wyjściem jest po prostu zdobycie szybszego chipa i wyprowadzenie produktu za drzwi. W podanym przykładzie oczywiście nie trzeba kontynuować próbkowania przy 10 000, jeśli ktoś ma polecenie - może nie wznowić próbkowania przez kwadrans bez szkody. Coraz trudniej jest zrozumieć, gdzie będzie miała znaczenie optymalizacja kierowana przez programistę.
Meredith Poor

1
Nie zawsze można „po prostu zdobyć szybszy chip i wydostać produkt z domu”. Rozważ przetwarzanie obrazu w czasie rzeczywistym. 640 x 480 pikseli / ramkę x 60 klatek / sekundę x N instrukcji na piksel dodaje się szybko. (Lekcja z przetwarzania obrazu w czasie rzeczywistym polega na tym, że pocisz się krwią przez jądra pikseli i prawie ignorujesz wszystko inne, ponieważ działa to raz na linię lub raz na łatkę lub raz na klatkę, w przeciwieństwie do setek razy na linię lub łatka lub dziesiątki lub setki tysięcy razy na klatkę.)
John R. Strohm,

@ JohnR.Strohm - biorąc pod uwagę przykład przetwarzania obrazu w czasie rzeczywistym, zakładam, że minimalne środowisko to 32 bity. Wychodząc na całość (ponieważ nie wiem, jak to praktyczne), wiele akceleratorów graficznych wbudowanych w układy scalone może być również użytecznych do rozpoznawania obrazów, więc układy ARM (na przykład), które mają zintegrowane silniki renderujące, mogą mieć dodatkowe użyteczne ALU do uznania. Do tego czasu użycie słowa kluczowego „register” do optymalizacji stanowi niewielką część problemu.
Meredith Poor

-3

Aby ustalić, czy słowo kluczowe w rejestrze ma jakieś znaczenie, małe przykładowe kody nie będą wystarczające. Oto kod c, który sugeruje mi, że słowo kluczowe register wciąż ma znaczenie. Ale może być inaczej z GCC na Linuksie, nie wiem. CZY rejestr int k & l będzie przechowywany w rejestrze procesora, czy nie? Użytkownicy Linuksa (szczególnie) powinni się kompilować z GCC i optymalizacją. W przypadku Borland bcc32 słowo kluczowe register wydaje się działać (w tym przykładzie), ponieważ operator & podaje kody błędów dla liczb całkowitych zadeklarowanych w rejestrze. UWAGA! Nie jest tak w przypadku małego przykładu z Borlandem na Windowsie! Aby naprawdę zobaczyć, co kompilator optymalizuje, czy nie, musi to być coś więcej niż mały przykład. Puste pętle nie wystarczą! Niemniej jednak - JEŻELI adres MOŻE zostać odczytany za pomocą & -operatora, zmienna nie jest przechowywana w rejestrze procesora. Ale jeśli nie można odczytać zmiennej zadeklarowanej w rejestrze (powodując kod błędu podczas kompilacji) - muszę założyć, że słowo kluczowe register faktycznie umieszcza zmienną w rejestrze procesora. Może się różnić na różnych platformach, nie wiem. (Jeśli to zadziała, liczba „tików” będzie znacznie niższa wraz z deklaracją rejestru.

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

} 

Nastąpi podział od zera powyżej, zmień {tmp + = ii / jj;} na {tmp + = jj / ii;} - naprawdę przepraszam za to
John P Eriksson

Niech k i ja zaczniemy od 1 - nie zero. Bardzo przepraszam.
John P Eriksson

3
Możesz edytować swoją odpowiedź zamiast pisać poprawki w komentarzach.
Jan Doggen
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.