Коли ключове слово зареєструвати насправді корисно в C?


10

Мене плутає використання registerключового слова у C. Зазвичай кажуть, що його використання не потрібне, як у цьому питанні про stackoverflow .

Чи є це ключове слово абсолютно зайвим у С завдяки сучасним компіляторам чи є ситуації, коли вони все ще можуть бути корисними? Якщо так, то в яких ситуаціях registerкорисне використання ключового слова?


4
Я думаю, що пов'язане питання та відповіді на нього такі ж, як ви можете очікувати тут. Тож нової інформації, яку ви можете отримати тут, не буде.
Uwe Plonus

@UwePlonus Я вважав те ж саме щодо constключового слова, але це питання виявило, що я помиляюся. Тож я зачекаю і побачу, що я отримаю.
Асеем Бансал

Я думаю, що constключове слово є чимось іншим проти реєстрації.
Uwe Plonus

4
Це корисно, якщо ви випадково повернетесь у часі і змушені використовувати один із ранніх компіляторів С. Крім того, що це зовсім не корисно, воно цілком застаріло роками.
JohnB

@UwePlonus Я просто мав на увазі, що можуть бути невідомі мені сценарії, в яких ключове слово може бути корисним.
Асеем Бансал

Відповіді:


11

Це не зайве з точки зору мови, це просто те, що використовуючи його, ви говорите компілятору, ви б "вважали за краще" мати змінну, що зберігається в регістрі. Однак існує абсолютно нульова гарантія, що це дійсно станеться під час виконання.


9
Більше того, майже завжди трапляється так, що компілятор найкраще знає, і ти марнуєш подих
Daniel Gratzer

6
@jozefg: ще гірше. Ви ризикуєте, що компілятор шанує ваш запит / підказку і в результаті створює гірший код.
Барт ван Іґен Шенау

9

Як уже згадувалося, оптимізатори компілятора, по суті, роблять registerключове слово застарілим для інших цілей, ніж запобігання збитку. Однак є цілі бази кодів, складені з відключеною оптимізацією ( -O0в gcc-говорінні ). Для такого коду registerключове слово може мати великий ефект. Зокрема, змінні, які в іншому випадку отримали б слот на стеку (тобто всі параметри функції та автоматичні змінні), можуть бути розміщені безпосередньо в реєстрі, якщо вони оголошені за допомогою registerключового слова.

Ось приклад із реального світу: припустимо, що деякі пошукові бази даних відбулися і що код пошуку вклав отриманий кордон у структуру C. Крім того, припустимо, що деякий підмножина цієї структури С потрібно скопіювати в іншу структуру - можливо, ця друга структура є записом кешу, який представляє метадані, що зберігаються в базі даних, які через обмеження пам'яті кешують лише підмножину кожного запису метаданих як збереженого в базі даних.

З огляду на функцію, яка приймає вказівник на кожен тип структури і єдиним завданням якого є копіювання деяких членів з початкової структури в другу структуру: змінні вказівника struct будуть жити в стеку. Оскільки призначення відбувається від одного члена структури до іншого, адреси структури для кожного завдання завантажуватимуться в реєстр, щоб забезпечити доступ членів структури, які копіюються. Якщо вказівники на структуру повинні бути оголошені за допомогою registerключового слова, адреси структур залишатимуться в регістрах, фактично вирізаючи інструкції для завантаження адреси в реєстр для кожного завдання.

Знову ж таки, пам’ятайте, що вищеописаний опис стосується неоптимізованого коду.


6

Ви в основному говорите компілятору, що не будете приймати адресу змінної, і компілятор може ніби потім зробити подальші оптимізації. Наскільки мені відомо, сучасні компілятори цілком здатні визначити, чи можна / слід зберігати змінну в реєстрі чи ні.

Приклад:

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

Dereference або взяти адресу?
detly

@detly: ви, звичайно, правильні
Лукас

0

У 16-бітові комп'ютерні дні часто потрібно було декілька регістрів, щоб виконати 32-бітні множення та ділення. Оскільки одиниці з плаваючою комою були включені в мікросхеми, а потім 64-розрядна архітектура «перейняла», і ширина регістрів, і кількість їх розширилися. Це врешті-решт призводить до повної реконструкції процесора. Див. Реєстрація файлів у Вікіпедії.

Коротше кажучи, вам знадобиться трохи часу, щоб зрозуміти, що насправді відбувається, якщо ви знаходитесь на 64-бітному чіпі X86 або ARM. Якщо ви користуєтеся 16-бітним вбудованим процесором, це насправді може отримати щось. Однак більшість маленьких вбудованих мікросхем не мають критичного часу - ваша мікрохвильова піч може відбирати ваш сенсорний панель 10000 разів на секунду - нічого, що напружує процесор 4 МГц.


1
4 MIPS / 10000 опитування / сек = 400 інструкцій / опитування. Це не стільки велика маржа, скільки ви хотіли б мати. Також зауважте, що досить багато 4 МГц процесорів були мікрокодовані всередині, що означає, що вони ніде не були близько 1 MIP / МГц.
Джон Р. Стром

@ JohnR.Strohm - Можуть виникнути ситуації, коли можна виправдати з'ясування, скільки саме циклів інструкцій буде потрібно, але, найчастіше, дешевший вихід зараз - це просто отримати швидший чіп і вивести продукт у двері. У наведеному прикладі, звичайно, не потрібно продовжувати вибірку на рівні 10 000, якщо є команда - вона може не відновити вибірку протягом чверті секунди без шкоди. Ставати все складніше зрозуміти, куди має значення важлива оптимізація, спрямована програмістом.
Мередіт Бідна

1
Не завжди можливо «просто отримати швидший чіп і дістати товар у двері». Розглянемо обробку зображень у реальному часі. 640x480 пікселів / кадр x 60 кадрів / секунда x N інструкцій на піксель швидко додає. (Урок з обробки зображень у реальному часі полягає в тому, що ви потієте кров’ю над своїми піксельними ядрами, а ви майже ігноруєте все інше, тому що воно працює один раз за рядком або один раз за патчем або один раз за кадр, на відміну від сотень разів на рядок або патч або десятки чи сотні тисяч разів на кадр.)
Джон Р. Стром

@ JohnR.Strohm - беручи приклад обробки зображень у реальному часі, я вважаю, що мінімальне середовище - 32 біта. Виходячи на кінцівку (тому що я не знаю, наскільки це практично), багато графічних прискорювачів, вбудованих у мікросхеми, також можуть бути використані для розпізнавання зображень, тому мікросхеми ARM (наприклад), які мають інтегровані двигуни візуалізації, можуть мати додаткові ALU, які можна використовувати для визнання. На той час використання ключового слова "зареєструватись" для оптимізації є невеликою частиною проблеми.
Мередіт Бідна

-3

Для того, щоб встановити, чи має ключове слово регістр якесь значення, крихітні приклади кодів не роблять. Ось с-код, який підказує мені, ключове слово регістр все ще має значення. Але це може бути інакше з GCC в Linux, я не знаю. Чи буде зареєстровано int k & l, буде збережено в регістрі процесора чи ні? Користувачі Linux (особливо) повинні компілювати з GCC та оптимізацією. З Borland bcc32 ключове слово регістр, як видається, функціонує (у цьому прикладі), оскільки & -operator надає коди помилок для реєстрованих цілих чисел. ПРИМІТКА! ЦЕ НЕ так у крихітному прикладі з Borland на Windows! Для того, щоб реально побачити, що оптимізує компілятор чи ні, він повинен бути більш ніж крихітним прикладом. Порожні петлі не робитимуть! Тим не менше - якщо адресу МОЖНА читати за допомогою & -operator, змінна не зберігається в регістрі процесора. Але якщо заявлену змінну реєстру неможливо прочитати (викликаючи код помилки при компіляції) - я повинен припустити, що ключове слово регістр насправді ставить змінну в регістр процесора. Це може відрізнятися на різних платформах, я не знаю. (Якщо вона спрацьовує, кількість оголошень "тиків" буде значно нижчою за декларацією регістра.

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

} 

Буде ділення з нулем вище, будь ласка, змініть {tmp + = ii / jj;} на {tmp + = jj / ii;} - по-справжньому вибачте за це
Джон П Еріксон

Нехай також k і я починається з 1 - не з нуля. Дуже шкода.
Джон П Ерікссон

3
Ви можете редагувати свою відповідь, а не писати виправлення в коментарях.
Ян Догген
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.