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

Какие адресные пространства памяти есть?

Какие формы адресных пространств памяти были использованы?

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

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

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

Полезная информация может включать:

  • Примеры компьютерных архитектур с различными адресными пространствами и описания этих пространств.
  • Примеры различных адресных пространств, которые все еще используются в машинах, которые в настоящее время производятся.
  • Ссылки на документацию или объяснение, особенно URL-адреса.
  • Разработка того, как адресные пространства мотивируют правила указателей на C/С++.

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

4b9b3361

Ответ 1

Почти все, что вы можете себе представить, вероятно, было использовано. первое основное разделение - между байтовой адресацией (все современные архитектуры) и адресации (до IBM 360/PDP-11, но Я думаю, что современные мэйнфреймы Unisys по-прежнему используются для разговора). В адреса, char* и void* часто бывают больше, чем a int*; даже если они не были больше, "селектор байтов" будут находиться в битах высокого порядка, которые должны быть равны 0, или будет игнорироваться для чего угодно, кроме байтов. (На PDP-10, например, если p был char*, (int)p < (int)(p+1) часто бывают ложными, хотя int и char* имеют одинаковые размер.)

Среди байт-адресуемых машин основные варианты сегментированы и несегментированные архитектуры. Оба по-прежнему широко распространены сегодня, хотя в случае Intel 32bit (сегментированный архитектура с 48-разрядными адресами), некоторые из них используемые ОС (Windows и Linux) искусственно ограничивают пользователей процессов в один сегмент, имитируя плоскую адресацию.

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

Ответ 2

Я бы сказал, что вы задаете не тот вопрос, кроме исторического любопытства.

Даже если ваша система использует плоское адресное пространство - даже если каждая система с настоящего момента и до конца времени использует плоское адресное пространство - вы все равно не можете рассматривать указатели как целые числа.

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

Для конкретного примера, три месяца назад в Valgrind появилась очень интересная ошибка:

https://sourceforge.net/p/valgrind/mailman/message/29730736/

(Нажмите "Просмотреть всю ветку", а затем найдите "неопределенное поведение".)

По сути, Вальгринд использовал указатели "меньше" и "больше", чтобы попытаться определить, находится ли автоматическая переменная в определенном диапазоне. Поскольку сравнения между указателями в разных агрегатах "не определены", Clang просто оптимизировал все сравнения, чтобы получить постоянную true (или false; я забыл).

Эта ошибка сама по себе породила fooobar.com/questions/274463/....

Таким образом, хотя первоначальные арифметические определения указателей могли быть ориентированы на реальные машины, и это само по себе может быть интересно, на самом деле это не имеет отношения к программированию сегодня. Сегодня важно то, что вы просто не можете предположить, что указатели ведут себя как целые числа, точка, независимо от системы, которую вы используете. "Неопределенное поведение" не означает "что-то смешное происходит"; это означает, что компилятор может предположить, что вы этим не занимаетесь. Когда вы это делаете, вы вводите противоречие в рассуждения компилятора; и из противоречия вытекает все что угодно... Это зависит только от того, насколько умен ваш компилятор.

И они становятся умнее все время.

Ответ 3

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

  1. Те, которые нацелены на машины с линейной моделью памяти, и которые спроектированы и/или сконфигурированы для использования в качестве "ассемблера высокого уровня" --something, авторы Стандарта прямо заявили, что не хотят этого исключать. Большинство реализаций ведут себя таким образом, когда оптимизации отключены.

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

  3. Те, чей дизайн и/или конфигурация делают их пригодными только для задач, которые не включают низкоуровневое программирование, включая clang и gcc, когда оптимизация включена.

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

Реализации третьего типа следует использовать только для программ, которым не нужно выполнять какие-либо задачи по управлению памятью или системному программированию. Поскольку стандарт не требует, чтобы все реализации были способны поддерживать такие задачи, некоторые разработчики компиляторов - даже при нацеливании на машины с линейными адресами - не пытаются поддерживать какую-либо из их полезной семантики. Даже некоторые принципы, такие как "сравнение на равенство между двумя действительными указателями", в худшем случае, либо приведут к 0 или 1, выбранному, возможно, неопределенным образом, не будут применяться к таким реализациям.

Ответ 4

Существуют различные формы банковской памяти.

Я работал над встроенной системой, которая имела 128 КБ общей памяти: 64 КБ ОЗУ и 64 КБ EPROM. Указатели были только 16-разрядными, поэтому указатель в ОЗУ мог иметь одно и то же значение указателя в СППЗУ, даже если они ссылались на разные области памяти.

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

Можно утверждать, что это было похоже на сегмент + смещение, а на аппаратном уровне это было по существу. Но сегмент (или, точнее, банк) был неявным от типа указателя и не сохранялся как значение указателя. Если вы проверите указатель в отладчике, вы просто увидите 16-битное значение. Чтобы узнать, было ли это смещение в ОЗУ или ПЗУ, вы должны были знать тип.

Например, Foo * может быть только в ОЗУ, а const Bar * может быть только в ПЗУ. Если бы вам пришлось скопировать Bar в ОЗУ, копия на самом деле была бы другого типа. (Это было не так просто, как const/non-const: все в ПЗУ было константным, но не все константы были в ПЗУ.)

Это было все в C, и я знаю, что мы использовали нестандартные расширения для этой работы. Я подозреваю, что компилятор C на 100%, вероятно, не справился бы с этим.