Подтвердить что ты не робот

Почему адрес-оператор ('&') может использоваться с объектами, объявленными с помощью спецификатора класса хранения регистров в С++?

В языке программирования C нам не разрешается использовать адрес-оператора (&) с переменными, объявленными с помощью спецификатора класса хранения регистра.

Он дает error: address of register variable ‘var_name’ requested

Но если мы создадим С++-программу и выполним одну и ту же задачу (т.е. используем переменную & с переменной хранения реестра), она не дает нам никакой ошибки.

например.

#include <iostream>
using namespace std;
int main()
{
    register int a;
    int * ptr;
    a = 5;
    ptr = &a;
    cout << ptr << endl;
    return 0;
}

Выход: -

0x7ffcfed93624

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

4b9b3361

Ответ 1

Ограничение на обращение адреса было намеренно удалено на С++ - для него не было никакой пользы, и это усложнило язык. (Например, что произойдет, если вы привяжете ссылку к переменной register?)

Ключевое слово register не очень много используется в течение многих лет - компиляторы очень хорошо разбираются в том, что сами записывать в регистры. Действительно, в С++ ключевое слово в настоящее время устарело и в конечном итоге будет удалено.

Ответ 2

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

Однако при этом существует множество требований. Для каждой архитектуры процессора многие разные:

  • Все процессоры имеют фиксированное количество регистров, но каждая модель процессора имеет другое число. В 80-е годы у вас могло быть 4, которые могли бы разумно использоваться для переменной register.
  • Большинство процессоров не поддерживают использование каждого регистра для каждой команды. В 80-е годы было не редкость иметь только один регистр, который вы могли бы использовать для сложения и вычитания, и вы, вероятно, не могли использовать тот же самый регистр, что и указатель.
  • Вызывающие соглашения продиктовали разные наборы регистров, которые можно было бы перезаписать подпрограммами, то есть вызовами функций.
  • Размер регистра различается между процессорами, поэтому бывают случаи, когда переменная register не вписывается в регистр.

Поскольку C предназначен для независимости от платформы, эти ограничения не могут быть соблюдены стандартом. Другими словами, хотя может быть невозможно скомпилировать процедуру с 20 register переменными для системы, в которой было только 4 машинных регистра, сама программа C не должна быть "неправильной", так как нет логической причины, по которой машина не может 20 регистров. Таким образом, класс хранения register всегда был лишь намеком на то, что компилятор может игнорировать, если конкретная целевая платформа не поддерживает его.

Неспособность ссылаться на регистр отличается. A register специально не обновляется в памяти и не сохраняется, если в память вносятся изменения; что весь смысл класса хранения. Поскольку они не предназначены для гарантированного представления в памяти, они не могут логически иметь адрес в памяти, который будет иметь смысл для внешнего кода, который может получить указатель. Регистры не имеют адреса для своего собственного процессора, и у них почти нет адреса, доступного для любого сопроцессора. Поэтому любая попытка получить ссылку на register всегда является ошибкой. Стандарт C мог бы обеспечить соблюдение этого правила.

Однако по мере развития вычислений были разработаны некоторые тенденции, которые ослабили цель самого класса хранения register:

  • Процессоры поставлялись с большим количеством регистров. Сегодня у вас, вероятно, есть не менее 16, и они могут, вероятно, все быть взаимозаменяемыми для большинства целей.
  • Многоядерные процессоры и распределенное исполнение кода стали очень распространенными; только одно ядро ​​имеет доступ к любому регистру и никогда не делится ими без использования памяти.
  • Алгоритмы выделения регистров переменным стали очень эффективными.

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

Поэтому, несмотря на то, что сегодня это так (и будет до тех пор, пока на компьютерах не будет даже регистров), что вы не можете логически ссылаться на регистр CPU, ожидается, что класс хранения register будет соблюден так много времени, что для стандарта неразумно требовать от компиляторов требовать, чтобы вы были логичным в своем использовании.

Ответ 3

Референсным регистром будет сам регистр. Если вызывающая функция передала ESI в качестве ссылочного параметра, то вызываемая функция будет использовать ESI в качестве параметра. Как указал Алан Стоукс, проблема в том, что другая функция также вызывает ту же функцию, но на этот раз с EDI в качестве того же ссылочного параметра.

Чтобы это работало, нужно было создать два перегруженных типа экземпляра вызываемой функции, один из которых принял ESI в качестве параметра, а EDI - в качестве параметра. Я не знаю, действительно ли какой-либо фактический компилятор С++ реализует такую ​​оптимизацию вообще, но так можно сделать.

Одним из примеров регистрации по ссылке является способ оптимизации std:: swap() (оба параметра являются ссылками), который часто заканчивается как встроенный код. Иногда не происходит смены: например, std:: swap (a, b), не происходит никакого обмена, вместо этого смысл a и b заменяется в следующем коде (ссылки на то, что стало ссылкой на b и vice Versa).

В противном случае опорный параметр заставит переменную располагаться в памяти вместо регистра.