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


89

У чому різниця між

int x=7;

і

register int x=7;

?

Я використовую С ++.


8
@GMan: ANSI C не дозволяє приймати адресу об’єкта реєстру; це обмеження не поширюється на C ++
Брайан Р. Бонді

1
@Brian: Хм, ти маєш рацію. Це просто в примітці (що, ймовірно, вона буде проігнорована, якщо адреса буде взята), але не передбачено. Добре знати. (Ну, начебто.: P)
GManNickG

8
Голосування за повторне відкриття registerмає різну семантику між С і С ++.
CB Bailey

3
як наслідок цього, в C можна заборонити перетворення масиву в покажчик, створивши регістр масиву: за register int a[1];допомогою цього оголошення ви не можете індексувати цей масив. Якщо ви намагаєтесь, ви робите UB
Йоганнес Шауб - litb

2
Справді, я проголосував за повторне відкриття. Я проголосував за закриття до того, як дізнався, що є різниця.
GManNickG

Відповіді:


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
Приклад останнього біта може бути корисним.
Dennis Zickefoose

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, це неправильно; register зарезервовано в 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 ++ без потоків та багатозадачності. Програма С ++ повинна була б володіти цілим процесором, щоб переконатися, що змінна 'register' не буде переміщена зі спеціальних регістрів CPU.
Сантьяго Віллафуерте

@SantiagoVillafuerte Ви хочете додати його, редагуючи відповідь?
ncomputers

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

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

Наведений приклад насправді використовує розширення явних змінних реєстру GCC , яке відрізняється від 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, оскільки посилання також має адресу, оскільки це вказівник const на стеці , який, як і вказівник, може бути оптимізований поза стеком при компіляції за допомогою -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має 0 ефект оптимізації, тому що якщо його можна внести до реєстру або зробити негайно, він завжди буде, а якщо не зможе - не буде; 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.