Зарегистрируйте ключевое слово в C ++


89

В чем разница между

int x=7;

а также

register int x=7;

?

Я использую C ++.


8
@GMan: ANSI C не позволяет брать адрес объекта регистра; это ограничение не распространяется на C ++
Брайан Р. Бонди

1
@ Брайан: Хм, ты прав. Сейчас это просто примечание (что, вероятно, будет проигнорировано, если адрес будет взят), но не обязательным. Хорошо знать. (Ну, вроде как.: P)
GManNickG 08

8
Голосование за повторное открытие registerимеет разную семантику в C и C ++.
CB Bailey

3
как следствие этого, в C можно запретить преобразование массива в указатель, создав регистр массива: register int a[1];с этим объявлением вы не можете индексировать этот массив. Если вы попытаетесь, вы сделаете UB
Johannes Schaub - litb

2
Действительно, я проголосовал за повторное открытие. Я проголосовал за закрытие, прежде чем узнал о разнице.
GManNickG 08

Ответы:


24

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

В C ++ 11 и более поздних версиях языка autoключевое слово было изменено, чтобы действовать как псевдотип для инициализируемых объектов, который компилятор автоматически заменяет типом инициализирующего выражения. Таким образом, в C ++ 03 объявление: auto int i=(unsigned char)5;было эквивалентно int i=5;использованию в контексте блока и auto i=(unsigned char)5;являлось нарушением ограничения. В C ++ 11 auto int i=(unsigned char)5;стало нарушением ограничения, а auto i=(unsigned char)5;стало эквивалентом auto unsigned char i=5;.


22
Может быть полезен пример последнего бита.
Деннис Зикефуз 08

14
Этот ответ больше не является правильным, с 2011 года ключевое слово autoнельзя просто опускать ... Возможно, вы могли бы обновить свой ответ.
Уолтер

2
@Walter: Вы можете процитировать, что изменилось? Я не следил за всеми изменениями языка.
supercat

2
@supercat, да, на данный момент, но registerон устарел, и будет предложение удалить его для C ++ 17.
Джонатан Уэйкли

3
Согласно en.cppreference.com/w/cpp/language/auto , сообщение C ++ 11 autoтеперь используется для автоматического определения типа. Но до этого он использовался для указания того, что вы хотите, чтобы ваша переменная сохранялась «автоматически» ( следовательно, в стеке, я полагаю) в отличие от ключевого слова register(означающего «регистр процессора»):
Гийом

97

register является подсказкой компилятору, советуя ему хранить эту переменную в регистре процессора вместо памяти (например, вместо стека).

Компилятор может следовать этому совету, а может и не следовать.

По словам Херба Саттера в статье «Ключевые слова, которых нет (или комментарии другого имени)» :

Спецификатор регистра имеет ту же семантику, что и автоматический спецификатор ...


2
Однако, начиная с C ++ 17, он устарел, не используется и зарезервирован.
ZachB

@ZachB, это неверно; Регистр зарезервирован в C ++ 17, но он по-прежнему работает и функционирует почти так же, как регистр C.
Льюис Келси,

@LewisKelsey Он не используется и зарезервирован в спецификации C ++ 17; он не входит storage-class-specifierв грамматику и не имеет определенной семантики. Соответствующий компилятор может выдать ошибку, как это делает Clang. Тем не менее, некоторые реализации все еще позволяют это и либо игнорируют (MSVC, ICC), либо используют в качестве подсказки по оптимизации (GCC). См. Open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0001r1.html . Однако в одном я ошибся: в C ++ 11 он устарел.
ZachB


26

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


9

Почти наверняка ничего.

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

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



7

registerКлючевое слово было полезно для:

  • Встроенная сборка.
  • Эксперт по программированию на C / C ++.
  • Объявление кэшируемых переменных.

Пример продуктивной системы, где registerтребовалось ключевое слово:

typedef unsigned long long Out;
volatile Out out,tmp;
Out register rax asm("rax");
asm volatile("rdtsc":"=A"(rax));
out=out*tmp+rax;

Он устарел с C ++ 11 и не используется и зарезервирован в C ++ 17 .


2
И я бы добавил, что ключевое слово 'register' будет полезно только в микроконтроллере, на котором запущена одна программа на C ++ без потоков и без многозадачности. Программа на C ++ должна будет владеть всем ЦП, чтобы гарантировать, что переменная 'register' не будет перемещена из специальных регистров ЦП.
Сантьяго Виллафуэрте,

@SantiagoVillafuerte хотите добавить, редактируя ответ?
ncomputers

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

1
@SantiagoVillafuerte На самом деле это не так, в многозадачных системах, когда контекстное переключение ОС, а не приложение, отвечает за сохранение / восстановление регистров. Поскольку вы не переключаете контекст после каждой инструкции ЦП, размещение данных в регистрах имеет большое значение. Другие ответы здесь (что компиляторы просто не заботятся о вашем мнении, когда дело доходит до распределения регистров) более точны.
Cubic

В показанном вами примере фактически используется расширение GCC Explicit Register Variables , которое отличается от registerспецификатора класса хранения и все еще поддерживается GCC.
ZachB

1

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

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


1

В НКУ 9.3, компиляции с использованием -std=c++2a, register производит предупреждения компилятора, но она все еще имеет желаемый эффект и ведет себя идентично Кассиопеян registerпри компиляции без -O1 - Ofast оптимизацию флаги в отношении этого ответа. Однако использование clang ++ - 7 вызывает ошибку компилятора. Так что да, registerоптимизации имеют значение только при стандартной компиляции без флагов оптимизации -O, но это базовые оптимизации, которые компилятор определит даже с -O1.

Единственное отличие состоит в том, что в C ++ вам разрешено использовать адрес регистровой переменной, что означает, что оптимизация происходит только в том случае, если вы не берете адрес переменной или ее псевдонимы (для создания указателя) или не берете ссылку его в коде (только на -O0, потому что ссылка также имеет адрес, потому что это константный указатель в стеке , который, как указатель, может быть оптимизирован вне стека при компиляции с использованием -Ofast, за исключением того, что они никогда не появятся в стеке с помощью -Ofast, потому что, в отличие от указателя, они не могут быть созданы volatileи их адреса не могут быть взяты), в противном случае он будет вести себя так, как вы не использовали register, и значение будет сохранено в стеке.

На -O0 другое отличие состоит в том, что const registerна gcc C и gcc C ++ не работают одинаково. На gcc C const registerведет себя так register, потому что область видимости блока constне оптимизирована на gcc. На clang C registerничего не делает, и constприменяются только оптимизации блочной области. В gcc C registerоптимизации применяются, но constв области видимости блока нет оптимизации. На GCC C ++, как registerи constблок-область применения оптимизаций комбината.

#include <stdio.h> //yes it's C code on C++
int main(void) {
  const register int i = 3;
  printf("%d", i);
  return 0;
}

int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3
  mov eax, DWORD PTR [rbp-4]
  mov esi, eax
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

register int i = 3;:

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  push rbx
  sub rsp, 8
  mov ebx, 3
  mov esi, ebx
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  mov rbx, QWORD PTR [rbp-8] //callee restoration
  leave
  ret

const int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov DWORD PTR [rbp-4], 3 //still saves to stack
  mov esi, 3 //immediate substitution
  mov edi, OFFSET FLAT:.LC0
  mov eax, 0
  call printf
  mov eax, 0
  leave
  ret

const register int i = 3;

.LC0:
  .string "%d"
main:
  push rbp
  mov rbp, rsp
  mov esi, 3 //loads straight into esi saving rbx push/pop and extra indirection (because C++ block-scope const is always substituted immediately into the instruction)
  mov edi, OFFSET FLAT:.LC0 // can't optimise away because printf only takes const char*
  mov eax, 0 //zeroed: https://stackoverflow.com/a/6212755/7194773
  call printf
  mov eax, 0 //default return value of main is 0
  pop rbp //nothing else pushed to stack -- more efficient than leave (rsp == rbp already)
  ret

registerсообщает компилятору: 1) сохранить локальную переменную в сохраненном регистре вызываемого объекта, в этом случае rbx, и 2) оптимизировать запись в стек, если адрес переменной никогда не используется . constсообщает компилятору немедленно заменить значение (вместо того, чтобы назначать ему регистр или загружать его из памяти) и записывать локальную переменную в стек в качестве поведения по умолчанию. const registerэто комбинация этих обнадеживающих оптимизаций. Это настолько компактно, насколько это возможно.

Кроме того, в gcc C и C ++ registerсоздается случайный 16-байтовый разрыв в стеке для первого локального узла в стеке, чего не происходит с const register.

Однако компиляция с использованием -Ofast; registerимеет нулевой эффект оптимизации, потому что, если его можно поместить в реестр или сделать немедленно, он всегда будет, а если нет, то не будет; constпо-прежнему оптимизирует нагрузку на C и C ++, но только в области файлов ; volatileпо-прежнему заставляет значения сохраняться и загружаться из стека.

.LC0:
  .string "%d"
main:
  //optimises out push and change of rbp
  sub rsp, 8 //https://stackoverflow.com/a/40344912/7194773
  mov esi, 3
  mov edi, OFFSET FLAT:.LC0
  xor eax, eax //xor 2 bytes vs 5 for mov eax, 0
  call printf
  xor eax, eax
  add rsp, 8
  ret
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.