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

202

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

  • 36
    Что делает ключевое слово регистра в C? игнорируется :)
  • 15
    @bestsss Не полностью игнорируется. Попробуйте получить адрес переменной register .
Показать ещё 1 комментарий
Теги:
memory
keyword

16 ответов

279
Лучший ответ

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

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

  • 16
    Ну, я поэкспериментировал с register, чтобы подправить свои представления ACM, и иногда это действительно помогало. Но вы действительно должны быть осторожны, потому что плохой выбор ухудшает производительность.
  • 76
    Хорошая причина не использовать регистр: вы не можете взять адрес переменной, объявленной регистром
Показать ещё 8 комментариев
59

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

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

  • 79
    На самом деле есть причина. Тот факт, что вы не можете получить адрес переменной, дает некоторые возможности оптимизации: компилятор может доказать, что переменная не будет псевдонимом.
  • 2
    @ Александр C. Достаточно справедливо. Я не думал об этом.
Показать ещё 3 комментария
34

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

  • 7
    Стоит добавить для людей, использующих C ++, C ++ позволяет вам взять адрес переменной регистра
  • 4
    @Will: ... но компилятор, скорее всего, в итоге проигнорирует ключевое слово. Смотри мой ответ.
Показать ещё 1 комментарий
17

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


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

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

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

16

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

12

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

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

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

  • 0
    "говорит компилятору .." нет, это не так. Все авто переменные имеют это свойство, если только вы не берете его адрес и не используете его способами, которые превышают определенные анализируемые применения. Таким образом, компилятор знает это из кода, независимо от того, используете ли вы ключевое слово register. Бывает так, что ключевое слово 'register' запрещает писать такую конструкцию, но если вы не используете ключевое слово и не берете адрес таким образом, то компилятор все еще знает, что это безопасно. Такая информация имеет решающее значение для оптимизации.
  • 1
    @greggo: Слишком плохой register вообще запрещает брать адрес, так как в противном случае было бы полезно сообщить компиляторам случаи, когда компилятор мог бы применить оптимизации регистра, несмотря на то, что адрес переменной передавался во внешнюю функцию (переменная имела бы чтобы быть сброшенным в память для этого конкретного вызова , но как только функция вернется, компилятор может снова обработать ее как переменную, адрес которой никогда не был взят).
Показать ещё 3 комментария
10

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

Фактически он четко определен стандартом C. Цитирование N1570 draft в разделе 6.7.1, пункт 6 (другие версии имеют одинаковую формулировку):

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

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

Существует несколько других (довольно неясных) правил, характерных для register -qualified объектов:

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

Может быть несколько других; загрузите черновик стандарта и найдите "register", если вы заинтересованы.

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

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

  • 0
    так действительно ли поведение этой программы не определено в соответствии со стандартом C? Хорошо ли это определено в C ++? Я думаю, что это хорошо определено в C ++.
  • 0
    @Destructor: Почему это будет неопределенным? Там нет register квалифицированного объекта массива, если вы об этом думаете.
Показать ещё 3 комментария
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 с общего времени, но я не запускал его достаточно много раз, чтобы узнать, было ли это даже статистически значимым.
  • 0
    С CLANG 5.0 нет никакой разницы, платформа AMD64. (Я проверил вывод ASM.)
4

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

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

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

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

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

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

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

Давайте посмотрим, почему!

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

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

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

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

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

c_register_side_effect_performance_boost

2

Storytime!

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

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

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

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

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

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

2

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

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

2

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

1

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

1

Компилятор Microsoft Visual С++ игнорирует ключевое слово register, когда включена глобальная оптимизация распределения регистров (флаг компилятора /Oe ).

См. зарегистрировать ключевое слово в MSDN.

0

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню