Nie mam pewności co do użycia słowa kluczowego register
w C. Powszechnie mówi się, że jego użycie nie jest ” t potrzebne jak w tym pytaniu o przepełnienie stosu .
Czy to słowo kluczowe jest całkowicie zbędne w C z powodu nowoczesnych kompilatorów, czy są sytuacje, w których nadal może być przydatne? Jeśli tak, w jakich sytuacjach użycie słowa kluczowego register
jest rzeczywiście przydatne?
Komentarze
- Myślę, że połączone pytanie i odpowiedzi na nie są takie same, jak można się tutaj spodziewać. Nie będzie więc żadnych nowych informacji, które można tu znaleźć.
- @UwePlonus Pomyślałem, że to samo co do
const
słowa kluczowego, ale to pytanie dowiodło, że się myliłem. Więc ' poczekam i zobaczę, co dostanę. - Myślę, że słowo kluczowe
const
jest czymś innym niż register. - To ' jest przydatne, jeśli przypadkowo cofniesz się w czasie i będziesz zmuszony użyć jednego z wczesnych kompilatorów C. Poza tym ' w ogóle nie jest użyteczny, ' jest od lat całkowicie przestarzały.
- @ UwePlonus Chodziło mi o to, że mogą istnieć nieznane mi scenariusze, w których słowo kluczowe może być przydatne.
Odpowiedź
To nie jest zbędne z punktu widzenia języka, po prostu używając go, mówisz kompilatorowi, że wolisz, aby zmienna była przechowywana w rejestrze. Jest jednak absolutnie zerowa gwarancja, że to faktycznie będzie zdarzają się w czasie wykonywania.
Komentarze
- Co więcej, ' prawie zawsze kompilator wie najlepiej, a ty ' marnujesz oddech
- @jozefg: jeszcze gorzej. Ryzykujesz, że kompilator spełni Twoje żądanie / wskazówkę i wygeneruje gorszy kod w rezultacie.
Odpowiedź
Jak już wspomniano, optymalizatory kompilatora zasadniczo ren der słowo kluczowe register
jest przestarzałe do celów innych niż zapobieganie aliasowaniu. Istnieją jednak całe bazy kodów, które są kompilowane z wyłączoną optymalizacją (-O0
w gcc-speak ). W przypadku takiego kodu słowo kluczowe register
może mieć świetny efekt. W szczególności zmienne, które w innym przypadku otrzymałyby miejsce na stosie (tj. Wszystkie parametry funkcji i zmienne automatyczne) mogą zostać umieszczone bezpośrednio w rejestrze, jeśli zostały zadeklarowane za pomocą register
słowo kluczowe.
Oto przykład ze świata rzeczywistego: załóżmy, że nastąpiło pobranie pewnej bazy danych i że kod pobierania upchnął pobraną krotkę do struktury C. Ponadto załóżmy, że pewien podzbiór tej struktury C. musi zostać skopiowany do innej struktury – może ta druga struktura jest zapisem pamięci podręcznej, który reprezentuje metadane przechowywane w bazie danych, która z powodu ograniczeń pamięci buforuje tylko podzbiór każdego rekordu metadanych przechowywany w bazie danych.
Biorąc pod uwagę funkcję, która pobiera wskaźnik do każdego typu struktury i której jedynym zadaniem jest kopiowanie niektórych elementów z początkowej struktury do drugiej struktury: zmienne wskaźnika struktury będą żyły na stosie. Gdy przypisania będą wykonywane z elementów jednej struktury innym adresom struktury będą, dla każdego przypisania, być załadowane do rejestru w celu uzyskania dostępu do kopiowanych elementów struktury. Gdyby wskaźniki struktur były zadeklarowane za pomocą słowa kluczowego register
, adresy struktur pozostałyby w rejestrach, skutecznie odcinając instrukcje ładowania adresu do rejestru dla każdego przypisania.
Ponownie pamiętaj, że powyższy opis dotyczy niezoptymalizowanego kodu.
Odpowiedź
Zasadniczo mówisz kompilatorowi, że nie przyjmiesz adresu zmiennej, a kompilator może wtedy rzekomo dalsze optymalizacje. O ile wiem, nowoczesne kompilatory są całkiem zdolne do określenia, 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; }
Komentarze
- Dereference lub weź adres?
- @detly: oczywiście masz rację
Odpowiedź
W 16-bitowych czasach komputerowych często potrzebowano wielu rejestrów, aby wykonywać mnożenie i dzielenie 32-bitowych. Jednostki zmiennoprzecinkowe zostały włączone do układów scalonych, a następnie „przejęły” architektury 64-bitowe, rozszerzając zarówno szerokość rejestrów, jak i ich liczbę. To ostatecznie prowadzi do całkowitej zmiany architektury procesora. Patrz Zarejestruj pliki w Wikipedii.
Krótko mówiąc, zajmie ci to trochę czasu, Dowiedz się, co się właściwie dzieje, jeśli używasz 64-bitowego układu X86 lub ARM.Jeśli korzystasz z 16-bitowego, wbudowanego procesora, może to faktycznie coś dać. Jednak większość małych wbudowanych chipów nie działa w żaden sposób krytyczny czasowo – Twoja kuchenka mikrofalowa może próbkować touchpad 10000 razy na sekundę – nic, co Procesor 4 MHz.
Komentarze
- 4 MIPS / 10 000 ankiet / s = 400 instrukcji / ankieta. To ' nie jest tak duże, jak ' chciałbyś mieć. Zwróć również uwagę, że sporo procesorów 4 MHz było wewnętrznie mikrokodowanych, co oznacza, że nie były one w pobliżu 1 MIP / MHz.
- @ JohnR.Strohm – Mogą zaistnieć sytuacje, w których można by uzasadnić ustalenie, ile dokładnie instrukcji cykli, które ' zajmie, ale często obecnie tańszym rozwiązaniem jest po prostu uzyskanie szybszego chipa i wyprowadzenie produktu z domu. W podanym przykładzie oczywiście nie ' nie trzeba kontynuować próbkowania przy 10000, jeśli ktoś ma polecenie – może nie wznowić próbkowania przez ćwierć sekundy bez szkody gotowy. Coraz trudniej jest dowiedzieć się, jakie znaczenie ma optymalizacja kierowana przez programistę.
- Nie zawsze jest możliwe ” po prostu uzyskać szybszy chip i produkt za drzwiami „. Rozważ przetwarzanie obrazu w czasie rzeczywistym. 640×480 pikseli / klatkę 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ą jądra pikseli i prawie ignorujesz wszystko inne, ponieważ działa raz na linię, raz na łatkę lub raz na klatkę, w przeciwieństwie do setek razy na linię lub patch lub dziesiątki lub setki tysięcy razy na klatkę.)
- @ JohnR.Strohm – biorąc przykład przetwarzania obrazu w czasie rzeczywistym, założyłbym, że minimalne środowisko to 32 bity. Wychodząc na prostą (ponieważ nie ' nie wiem, jakie to praktyczne) wiele akceleratorów graficznych wbudowanych w chipy może być również użytecznych do rozpoznawania obrazu, więc chipy ARM (na przykład ), które mają zintegrowane silniki renderujące, mogą mieć dodatkowe jednostki ALU nadające się do rozpoznania. Do tego czasu użycie słowa kluczowego ' register ' do optymalizacji stanowi niewielką część problemu.
Odpowiedź
Aby ustalić, czy słowo kluczowe rejestru ma jakiekolwiek znaczenie, małe przykładowe kody nie działają. Oto kod c co sugeruje mi, słowo register nadal ma znaczenie, ale może być inaczej z GCC na Linuksie, nie wiem. CZY rejestr int k & będzie przechowywany w rejestrze procesora, czy nie? Użytkownicy Linuksa (szczególnie) powinni kompilować się 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 dotyczy to małego przykładu z Borlandem w systemie Windows! Aby naprawdę zobaczyć, co kompilator optymalizuje, a co nie, musi to być coś więcej niż mały przykład. Puste pętle nie są „nie do zrobienia! Niemniej jednak – JEŚLI adres MOŻE zostać odczytany przez operatora &, zmienna nie jest przechowywana w rejestrze procesora. Ale jeśli zmiennej zadeklarowanej w rejestrze nie można odczytać (powoduje to 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 zaznaczeń będzie znacznie mniejsza w przypadku deklaracji 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=ⅆ ep=&e; eep=ⅇ fp=&f; ffp=&ff; gp=&g; ggp=≫ hp=&h; hhp=&hh; ip=&i; iip=ⅈ 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; }
Komentarze
- Powyżej będzie podział z zerem, zmień {tmp + = ii / jj;} na {tmp + = jj / ii;} – naprawdę przepraszam za to
- Pozwól także k i i zacznij od 1 – nie zero. Bardzo przepraszam.
- Możesz edytować swoją odpowiedź zamiast wpisywać poprawki w komentarzach.