«Зарегистрировать» ключевое слово в C?


273

Что делает registerключевое слово на языке Си? Я читал, что он используется для оптимизации, но не четко определен ни в одном стандарте. Это все еще актуально и если да, то когда бы вы его использовали?


42
Что делает ключевое слово регистра в C? игнорируется :)
bestsss

19
@bestsss Не полностью игнорируется. Попробуйте получить адрес registerпеременной.
qrdl

4
Код, который вы читаете, старый youtube.com/watch?v=ibF36Yyeehw#t=1827
Полковник Паник

Ответы:


341

Это подсказка компилятору, что переменная будет интенсивно использоваться, и вы порекомендуете сохранить ее в регистре процессора, если это возможно.

Большинство современных компиляторов делают это автоматически и лучше выбирают их, чем мы, люди.


18
Ну, я поэкспериментировал с register, чтобы подправить свои представления ACM, и иногда это действительно помогало. Но вы действительно должны быть осторожны, потому что плохой выбор ухудшает производительность.
ypnos

81
Хорошая причина не использовать 'register': вы не можете взять адрес переменной, объявленной как 'register'
Адам Розенфилд

23
Обратите внимание, что некоторые / многие компиляторы будут полностью игнорировать ключевое слово register (что вполне допустимо).
Евро Мицелли

5
ypnos: На самом деле скорость решения проблем ACM ICPC во многом зависит от выбора алгоритма, чем от такой микрооптимизации. 5-секундного ограничения времени обычно достаточно для правильного решения, особенно при использовании C вместо Java.
Джои

66
@Euro: Вы, наверное, знаете это, но для ясности, компилятор должен предотвращать захват адреса registerпеременной; это единственный обязательный эффект registerключевого слова. Даже этого достаточно для улучшения оптимизации, потому что становится тривиально сказать, что переменная может быть изменена только внутри этой функции.
Дейл Хэгглунд

69

Я удивлен, что никто не упомянул, что вы не можете получить адрес переменной регистра, даже если компилятор решает сохранить переменную в памяти, а не в регистре.

Таким образом, если registerвы ничего не выиграете (в любом случае компилятор сам решит, куда поместить переменную) и потеряет &оператор - нет причин его использовать.


94
На самом деле есть причина. Тот факт, что вы не можете получить адрес переменной, дает некоторые возможности оптимизации: компилятор может доказать, что переменная не будет псевдонимом.
Александр С.

8
Известно, что компиляторы ужасно доказывают, что псевдонимы не встречаются в нетривиальных случаях, поэтому registerэто полезно, даже если компилятор не помещает их в регистр.
Майлз Рут

2
@AlexandreC, Miles, компиляторы прекрасно справляются с проверкой того, берется ли & переменная где-либо. Таким образом, независимо от других трудностей, связанных с обнаружением псевдонимов, повторение, которое ничего не покупает Когда K + R впервые создал C, было действительно полезно заранее знать, что & не будет использоваться, поскольку этот компилятор фактически принял решение о распределении регистров, увидев объявление, прежде чем рассматривать следующий код. Вот почему запрет на месте. Ключевое слово 'register' по сути устарело.
Грегго

25
Эта логика constтакже бесполезна, так как она ничего не выигрывает, вы только теряете возможность изменять переменную. registerможет быть использован, чтобы убедиться, что никто не заберет адрес переменной в будущем, не задумываясь. У меня никогда не было причин использовать registerвсе же.
Тор Клингберг

34

Он говорит компилятору попытаться использовать регистр ЦП вместо ОЗУ для хранения переменной. Регистры находятся в центральном процессоре и имеют гораздо более быстрый доступ, чем оперативная память. Но это всего лишь предложение для компилятора, и оно может не исполниться.


8
Стоит добавить, что для людей, использующих C ++, C ++ позволяет вам взять адрес переменной регистра
Will

5
@Will: ... но компилятор, скорее всего, в итоге проигнорирует ключевое слово. Смотри мой ответ.
bwDraco

Да, кажется, что 'register' - это плацебо в C ++, он просто позволяет компилировать код C как C ++. И было бы бессмысленно запрещать & var, позволяя передавать его по ссылке или по const-ссылке, а без передачи по ссылке вы серьезно сломали C ++.
Грегго

22

Я знаю, что этот вопрос касается C, но тот же вопрос для C ++ был закрыт как точная копия этого вопроса. Таким образом, этот ответ может не относиться к C.


Последний вариант стандарта C ++ 11, N3485 , говорит об этом в 7.1.1 / 3:

registerСпецификатор намек на реализацию , что переменная так объявлено будет в значительной степени используются. [ примечание: подсказка может быть проигнорирована и в большинстве реализаций она будет игнорироваться, если адрес переменной взят. Это использование устарело ... - примечание ]

В C ++ (но не в C) стандарт не устанавливает, что вы не можете взять адрес объявленной переменной register; однако, поскольку переменная, хранящаяся в регистре ЦП в течение всего времени ее существования, не имеет связанной с ней ячейки памяти, попытка получить ее адрес будет недопустимой, и компилятор проигнорирует registerключевое слово, чтобы разрешить получение адреса.


17

Это не актуально как минимум 15 лет, так как оптимизаторы принимают лучшие решения по этому поводу, чем вы. Даже когда это было уместно, в архитектуре ЦП с большим количеством регистров было гораздо больше смысла, чем в SPARC или M68000, чем в Intel с его небольшим количеством регистров, большинство из которых зарезервированы компилятором для собственных целей.


13

Фактически, регистр сообщает компилятору, что переменная не имеет псевдонима с чем-либо еще в программе (даже с символами).

Это может быть использовано современными компиляторами в самых разных ситуациях и может помочь компилятору в сложном коде совсем немного - в простом коде компиляторы могут понять это самостоятельно.

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


"говорит компилятору .." нет, это не так. Все авто переменные имеют это свойство, если только вы не берете его адрес и не используете его способами, которые превышают определенные анализируемые применения. Таким образом, компилятор знает это из кода, независимо от того, используете ли вы ключевое слово register. Случается, что ключевое слово «register» запрещает написание такой конструкции, но если вы не используете ключевое слово и не берете адрес таким образом, то компилятор все еще знает, что это безопасно. Такая информация имеет решающее значение для оптимизации.
Грегго

1
@greggo: Жаль register, что вообще запрещает брать адрес, так как в противном случае было бы полезно сообщить компиляторам случаи, когда компилятор мог бы применить оптимизацию регистра, несмотря на то, что адрес переменной передавался во внешнюю функцию (переменная должна была бы быть сброшен в память для этого конкретного вызова , но как только функция вернется, компилятор может снова обработать ее как переменную, адрес которой никогда не был взят).
суперкат

@supercat Я думаю, что с компилятором все равно будет очень непросто поговорить. Если это то, что вы хотите сказать компилятору, вы можете сделать это, скопировав первую переменную во вторую, в которой нет символа «&», и затем никогда больше не используйте первую.
Грегго

1
@greggo: Говорят , что , если barэто registerпеременная, компилятор может в своем досуге заменить foo(&bar);с int temp=bar; foo(&temp); bar=temp;, но принимая адрес barбудет запрещен в большинстве других контекстах не могли бы показаться чрезмерно сложному правилу. Если бы переменная могла быть сохранена в регистре, подстановка сделала бы код меньше. Если переменную все равно нужно будет хранить в ОЗУ, подстановка сделает код больше. Если оставить вопрос о том, делать ли подстановку до компилятора, в обоих случаях код будет лучше.
суперкат

1
@greggo: Разрешение уточнения registerглобальных переменных, независимо от того, позволяет ли компилятор брать адрес, позволит провести приятную оптимизацию в тех случаях, когда встроенная функция, использующая глобальную переменную, неоднократно вызывается в цикле. Я не могу придумать какой-либо другой способ сохранить эту переменную в регистре между итерациями цикла - не так ли?
Суперкат

13

Я читал, что он используется для оптимизации, но не четко определен ни в одном стандарте.

На самом деле это будет четко определено стандартом C. Цитируя черновой вариант 6.7.1 раздела 6 N1570 (другие версии имеют такую ​​же формулировку):

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

Унарный &оператор не может применяться к объекту, определенному с помощью register, и registerне может использоваться во внешнем объявлении.

Есть несколько других (довольно непонятных) правил, специфичных для register-качественных объектов:

  • Определение объекта массива с registerнеопределенным поведением.
    Исправление: Допустимо определять объект массива register, но с таким объектом ничего полезного сделать нельзя (для индексации массива требуется адрес его начального элемента).
  • Спецификатор _Alignas(новый в C11) не может быть применен к такому объекту.
  • Если имя параметра, переданное va_startмакросу, является register-qualified, поведение не определено.

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

Как следует из названия, первоначальный смысл registerсостоял в том, чтобы требовать сохранения объекта в регистре процессора. Но с улучшениями в оптимизации компиляторов это стало менее полезным. Современные версии стандарта C не ссылаются на регистры ЦП, потому что они больше (не должны) предполагать, что есть такая вещь (есть архитектуры, которые не используют регистры). Общепринятым является то, что применение registerк объявлению объекта с большей вероятностью ухудшит сгенерированный код, поскольку это мешает распределению регистров собственного компилятора. Еще может быть несколько случаев, когда это полезно (скажем, если вы действительно знаете, как часто к переменной обращаются, и ваши знания лучше, чем то, что может вычислить современный оптимизирующий компилятор).

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


так действительно ли поведение этой программы не определено в соответствии со стандартом C? Это хорошо определено в C ++? Я думаю, что это хорошо определено в C ++.
Деструктор

@Destructor: Почему это будет неопределенным? Там нет register-оквалифицированного объекта массива, если вы об этом думаете.
Кит Томпсон,

Извините, я забыл написать ключевое слово register в объявлении массива в main (). Это хорошо определено в C ++?
Разрушитель

Я ошибся в определении registerобъектов массива; см обновленную первую точку пули в моем ответе. Законно определить такой объект, но вы ничего не можете с ним сделать. Если вы добавите registerк определению sв вашем примере , программа является недопустимой (нарушение ограничений) в C. C ++ не накладывает те же ограничения register, поэтому программа будет действительной C ++ (но использование registerбудет бессмысленным).
Кит Томпсон

@KeithThompson: registerКлючевое слово могло бы служить полезной цели, если было бы законно брать адрес такой переменной, но только в тех случаях, когда на семантику не повлияло бы копирование переменной во временную, когда берется ее адрес, и перезагрузка ее из временной в следующей точке последовательности. Это позволило бы компиляторам предполагать, что переменная может быть безопасно сохранена в регистре при всех обращениях к указателю при условии, что она сбрасывается в любом месте, где взят ее адрес.
суперкат

9

Время историй!

C, как язык, является абстракцией компьютера. Он позволяет вам делать то, что делает компьютер, то есть манипулировать памятью, выполнять математические операции, печатать вещи и т. Д.

Но C это только абстракция. И, в конечном счете, от вас извлекается язык ассемблера. Сборка - это язык, который читает процессор, и если вы используете его, вы делаете вещи с точки зрения процессора. Что делает процессор? По сути, он читает из памяти, выполняет математику и пишет в память. Процессор не просто выполняет математические операции с числами в памяти. Во-первых, вы должны переместить число из памяти в память внутри процессора, называемое регистром, Когда вы закончите делать с этим номером все, что вам нужно, вы можете переместить его обратно в обычную системную память. Зачем вообще использовать системную память? Количество регистров ограничено. Вы получаете только около ста байтов в современных процессорах, а старые популярные процессоры были еще более фантастически ограничены (у 6502 было 3 8-битных регистра для бесплатного использования). Итак, ваша средняя математическая операция выглядит так:

load first number from memory
load second number from memory
add the two
store answer into memory

Многое из этого ... не математика. Эти операции загрузки и хранения могут занимать до половины вашего времени обработки. C, будучи абстракцией компьютеров, избавил программиста от беспокойства по поводу использования и манипулирования регистрами, а поскольку число и тип различаются для разных компьютеров, C возлагает ответственность за распределение регистров исключительно на компилятор. За одним исключением.

Когда вы объявляете переменную register, вы говорите компилятору: «Да, я намерен использовать эту переменную много и / или быть недолгим. На вашем месте я бы попытался сохранить ее в реестре». Когда стандарт C говорит, что компиляторы на самом деле ничего не должны делать, это потому, что стандарт C не знает, для какого компьютера вы компилируете, и это может быть похоже на 6502 выше, где все 3 регистра необходимы просто для работы и нет запасного регистра, чтобы сохранить ваш номер. Однако, когда он говорит, что вы не можете взять адрес, это потому, что регистры не имеют адресов. Это руки процессора. Поскольку компилятору не нужно давать вам адрес, и поскольку он вообще не может иметь адреса, для компилятора теперь доступно несколько оптимизаций. Это может, скажем, сохранить номер в реестре всегда. Это не не нужно беспокоиться о том, где он хранится в памяти компьютера (кроме необходимости вернуть его обратно). Он может даже поместить его в другую переменную, передать другому процессору, изменить местоположение и т. Д.

tl; dr: недолговечные переменные, которые делают много математики. Не объявляй слишком много сразу.


5

Вы возитесь со сложным алгоритмом раскраски графа компилятора. Это используется для распределения регистров. Ну, в основном. Это действует как подсказка для компилятора - это правда. Но не игнорируется полностью, поскольку вам не разрешено брать адрес переменной регистра (помните, что компилятор, теперь по вашему желанию, будет пытаться действовать по-другому). Что в некотором смысле говорит вам не использовать его.

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

Но, как я уже сказал, устаревший не означает, что вы не можете его использовать.


13
Некоторые из старых аппаратных средств имели больше регистров, чем современные машины Intel. Число регистров не имеет никакого отношения к возрасту и всему, что связано с архитектурой процессора.
Просто мое правильное мнение

2
@JUSTMYcorrectOPINION Действительно, в X86 в основном их шесть, а максимум 1 или 2, чтобы посвятить себя «регистрации». На самом деле, поскольку так много кода было написано или перенесено на машину с плохим регистром, я подозреваю, что это в значительной степени способствовало превращению ключевого слова «регистр» в плацебо - нет смысла подсказывать регистры, когда их нет. Здесь мы находимся 4 с лишним года спустя и, к счастью, x86_64 поднял его до 14, и ARM теперь тоже важная вещь.
Грегго

4

Просто небольшая демонстрация (без каких-либо реальных целей) для сравнения: при удалении registerключевых слов перед каждой переменной этот фрагмент кода занимает 3,41 секунды на моем i7 (GCC), а register тот же код завершается за 0,7 секунды.

#include <stdio.h>

int main(int argc, char** argv) {

     register int numIterations = 20000;    

     register int i=0;
     unsigned long val=0;

    for (i; i<numIterations+1; i++)
    {
        register int j=0;
        for (j;j<i;j++) 
        {
            val=j+i;
        }
    }
    printf("%d", val);
    return 0;
}

2
С gcc 4.8.4 и -O3 разницы нет. Без -O3 и 40000 итераций я получаю, может быть, на 50 мс меньше за 1,5 с общего времени, но я не запускал его достаточно много раз, чтобы узнать, было ли это даже статистически значимым.
zstewart

С CLANG 5.0 нет никакой разницы, платформа AMD64. (Я проверил вывод ASM.)
ern0

4

Я проверил ключевое слово register в QNX 6.5.0, используя следующий код:

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <sys/neutrino.h>
#include <sys/syspage.h>

int main(int argc, char *argv[]) {
    uint64_t cps, cycle1, cycle2, ncycles;
    double sec;
    register int a=0, b = 1, c = 3, i;

    cycle1 = ClockCycles();

    for(i = 0; i < 100000000; i++)
        a = ((a + b + c) * c) / 2;

    cycle2 = ClockCycles();
    ncycles = cycle2 - cycle1;
    printf("%lld cycles elapsed\n", ncycles);

    cps = SYSPAGE_ENTRY(qtime) -> cycles_per_sec;
    printf("This system has %lld cycles per second\n", cps);
    sec = (double)ncycles/cps;
    printf("The cycles in seconds is %f\n", sec);

    return EXIT_SUCCESS;
}

Я получил следующие результаты:

-> 807679611 прошедших циклов

-> Эта система имеет 3300830000 циклов в секунду

-> Циклы в секундах ~ 0,244600

А теперь без регистрации int:

int a=0, b = 1, c = 3, i;

Я получил:

-> 1421694077 прошедших циклов

-> Эта система имеет 3300830000 циклов в секунду

-> Циклы в секундах ~ 0,430700


2

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

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


2

В семидесятые годы, в самом начале языка Си, было введено ключевое слово register, чтобы позволить программисту давать подсказки компилятору, сообщая ему, что переменная будет использоваться очень часто, и что следует сохранить его значение в одном из внутренних регистров процессора.

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

Поэтому многие люди ошибочно рекомендуют не использовать ключевое слово register.

Посмотрим почему!

Ключевое слово register имеет связанный побочный эффект: вы не можете ссылаться (получить адрес) на переменную типа регистра.

Люди, советующие другим не использовать регистры, ошибочно воспринимают это как дополнительный аргумент.

Однако тот простой факт, что вы не можете получить адрес переменной регистра, позволяет компилятору (и его оптимизатору) знать, что значение этой переменной нельзя изменить косвенно через указатель.

Когда в определенной точке потока команд переменная регистра имеет свое значение, назначенное в регистре процессора, и регистр не использовался, поскольку для получения значения другой переменной компилятор знает, что ему не нужно повторно загружать значение переменной в этом регистре. Это позволяет избежать дорогостоящего бесполезного доступа к памяти.

Проведите собственные тесты, и вы получите значительное улучшение производительности в самых внутренних циклах.

c_register_side_effect_performance_boost


1

В поддерживаемых C-компиляторах он пытается оптимизировать код, чтобы значение переменной сохранялось в фактическом регистре процессора.



1

Ключевое слово Register указывает компилятору сохранять конкретную переменную в регистрах ЦП, чтобы она была доступна быстро. С точки зрения программиста, ключевое слово register используется для переменных, которые интенсивно используются в программе, так что компилятор может ускорить код. Хотя от компилятора зависит, будет ли переменная храниться в регистрах ЦП или основной памяти.


0

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

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


0

Вывод gcc 9.3 asm без использования флагов оптимизации (все в этом ответе относится к стандартной компиляции без флагов оптимизации):

#include <stdio.h>
int main(void) {
  int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 3
        add     DWORD PTR [rbp-4], 1
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave 
        ret
#include <stdio.h>
int main(void) {
  register int i = 3;
  i++;
  printf("%d", i);
  return 0;
}
.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        push    rbx
        sub     rsp, 8
        mov     ebx, 3
        add     ebx, 1
        mov     esi, ebx
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        add     rsp, 8
        pop     rbx
        pop     rbp
        ret

Это заставляет ebxбыть использовано для вычисления, то есть его нужно поместить в стек и восстановить в конце функции, потому что он сохранен. registerпроизводит больше строк кода и 1 запись в память и 1 чтение в память (хотя реально это можно было бы оптимизировать до 0 R / W, если бы вычисление было выполнено esi, что и происходит с использованием C ++ const register). Неиспользование registerвызывает 2 записи и 1 чтение (хотя сохранение при загрузке будет происходить при чтении). Это связано с тем, что значение должно присутствовать и обновляться непосредственно в стеке, чтобы правильное значение можно было прочитать по адресу (указателю). registerне имеет этого требования и не может быть указано constи registerв основном противоположность volatileи использованиеvolatileпереопределит оптимизацию const в области видимости файлов и блоков, а также registerоптимизации в области видимости блоков. const registerи registerбудет выдавать идентичные выходные данные, потому что const ничего не делает с C на уровне блока, поэтому registerприменяются только оптимизации.

На clang registerигнорируется, но constоптимизация все же происходит.

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