Когда ключевое слово register действительно полезно в C?


10

Я запутался в использовании registerключевого слова в C. Обычно говорят, что его использование не нужно, как в этом вопросе о stackoverflow .

Является ли это ключевое слово полностью избыточным в C из-за современных компиляторов или есть ситуации, в которых оно все еще может быть полезным? Если да, то в каких ситуациях использование registerключевого слова действительно полезно?


4
Я думаю, что связанный вопрос и ответы на него такие же, как вы можете ожидать здесь. Так что не будет никакой новой информации, которую вы можете получить здесь.
Уве Плонус

@UwePlonus Я тоже думал о constключевом слове, но этот вопрос доказал, что я ошибался. Поэтому я подожду и посмотрю, что я получу.
Aseem Bansal

Я думаю, что constключевое слово отличается от регистра.
Уве Плонус

4
Это полезно, если вы случайно вернетесь в прошлое и будете вынуждены использовать один из ранних компиляторов Си. Кроме того, это вообще бесполезно, годами оно полностью устарело.
JohnB

@UwePlonus Я просто имел в виду, что могут быть неизвестные мне сценарии, в которых ключевое слово может быть полезным.
Aseem Bansal

Ответы:


11

Это не избыточно с точки зрения языка, просто используя его, вы говорите компилятору, что вы «предпочли бы» иметь переменную, хранящуюся в регистре. Однако абсолютно нулевая гарантия того, что это действительно произойдет во время выполнения.


9
Более того, почти всегда случается так, что компилятор знает лучше, а вы зря тратите время
Даниэль Гратцер,

6
@jozefg: еще хуже. Вы рискуете, что компилятор выполнит ваш запрос / подсказку и в результате выдаст худший код.
Барт ван Инген Шенау

9

Как уже упоминалось, оптимизаторы компилятора по сути делают registerключевое слово устаревшим для целей, отличных от предотвращения алиасинга. Тем не менее, существуют целые кодовые базы, которые скомпилированы с отключенной оптимизацией ( -O0на языке gcc ). Для такого кода registerключевое слово может иметь большой эффект. В частности, переменные, которые иначе получили бы слот в стеке (то есть все параметры функции и автоматические переменные), могут быть помещены непосредственно в регистр, если объявлены с registerключевым словом.

Вот реальный пример: предположим, что произошел некоторый поиск в базе данных и что код поиска вставил полученный кортеж в структуру C. Кроме того, предположим, что некоторое подмножество этой структуры C необходимо скопировать в другую структуру - возможно, эта вторая структура является записью в кэш, которая представляет метаданные, хранящиеся в базе данных, которые из-за ограничений памяти кэшируют только подмножество каждой записи метаданных, как хранимые в базе данных.

При наличии функции, которая получает указатель на каждый тип структуры и единственной задачей которого является копирование некоторых членов из исходной структуры во вторую структуру: переменные указателя структуры будут жить в стеке. Поскольку назначения происходят от членов одной структуры к другим, адреса структуры будут для каждого назначения загружаться в регистр для обеспечения доступа к членам структуры, которые копируются. Если указатели структуры должны были быть объявлены с registerключевым словом, адреса структур оставались бы в регистрах, эффективно вырезая инструкции загрузки адреса в регистр для каждого назначения.

Опять же, помните, что приведенное выше описание относится к неоптимизированному коду.


6

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

Пример:

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

Разыменование или взять адрес?
детально

@ detly: вы, конечно, правы
Лукас

0

В 16-битные компьютерные дни часто требовалось несколько регистров для выполнения 32-битных умножений и делений. Поскольку модули с плавающей запятой были встроены в микросхемы, а затем «взяли верх» 64-разрядные архитектуры, ширина регистров и их число расширились. В конечном итоге это приводит к полной реорганизации процессора. Смотрите Регистрация файлов в Википедии.

Короче говоря, вам понадобится немного времени, чтобы понять, что на самом деле происходит, если вы используете 64-битный чип X86 или ARM. Если вы используете 16-разрядный встроенный процессор, это может вам кое-что дать. Тем не менее, большинство небольших встроенных микросхем не запускают ничего критичного по времени - ваша микроволновая печь может отбирать данные у сенсорной панели 10 000 раз в секунду - ничего, что напрягает процессор с частотой 4 МГц.


1
4 MIPS / 10000 опросов / сек = 400 инструкций / опрос. Это не так много, как вы хотели бы иметь. Также отметим, что довольно много процессоров с частотой 4 МГц были внутренне микрокодированы, а это означает, что они не были близки к 1 Мп / МГц.
Джон Р. Штром

@ JohnR.Strohm - Могут быть ситуации, когда можно точно определить, сколько циклов инструкций потребуется, но зачастую более дешевым выходом является просто получить более быстрый чип и вывести продукт на улицу. В приведенном примере, конечно, не нужно продолжать выборку на 10000, если у кого-то есть команда - она ​​может не возобновить выборку в течение четверти секунды без вреда для здоровья. Становится все труднее выяснить, где программирование направлено на оптимизацию.
Мередит Бедный

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

@ JohnR.Strohm - если взять пример обработки изображений в реальном времени, я бы предположил, что минимальное окружение составляет 32 бита. Выходя из строя (потому что я не знаю, насколько это практично), многие графические ускорители, встроенные в микросхемы, могут также использоваться для распознавания изображений, поэтому микросхемы ARM (например), которые имеют встроенные механизмы рендеринга, могут иметь дополнительные ALU, которые можно использовать для признания. К тому времени использование ключевого слова «register» для оптимизации стало крошечной частью проблемы.
Мередит Бедный

-3

Чтобы определить, имеет ли ключевое слово register какое-либо значение, крошечные примеры кодов не подойдут. Вот c-код, который подсказывает мне, ключевое слово register по-прежнему имеет значение. Но с GCC в Linux все может быть иначе, я не знаю. Будет ли регистр int k & l храниться в регистре процессора или нет? Пользователи Linux (особенно) должны компилировать с GCC и оптимизацией. В Borland bcc32 ключевое слово register работает (в этом примере), так как & -operator выдает коды ошибок для объявленных в регистре целых чисел. НОТА! Это НЕ случай с крошечным примером с Borland на Windows! Чтобы действительно увидеть, что оптимизирует компилятор или нет, это должен быть более чем крошечный пример. Пустые петли не будут делать! Тем не менее, если адрес МОЖЕТ быть прочитан оператором &, переменная не сохраняется в регистре ЦП. Но если объявленная переменная регистра не может быть прочитана (вызывая код ошибки при компиляции) - я должен предположить, что ключевое слово register действительно помещает переменную в регистр CPU. Это может отличаться на разных платформах, я не знаю. (Если это сработает, количество «тиков» будет намного меньше при объявлении регистра.

/* 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;} - очень сожалею об этом
John P Eriksson

Также позвольте k, и я начинаю с 1 - не ноль. Очень жаль.
Джон П Эрикссон

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