Как работали 16-битные компиляторы C?
Модель памяти C, с использованием арифметики указателя и всего, кажется, моделирует плоское адресное пространство. 16-разрядные компьютеры используют сегментированный доступ к памяти. Как 16-разрядные компиляторы C справились с этой проблемой и имитировали плоское адресное пространство с точки зрения программиста C? Например, грубо говоря, какие инструкции на языке ассемблера будут компилировать следующий код на 8086?
long arr[65536]; // Assume 32 bit longs.
long i;
for(i = 0; i < 65536; i++) {
arr[i] = i;
}
Ответы
Ответ 1
Как работали 16-битные компиляторы C этот вопрос и имитировать плоский адрес пространство с точки зрения C программист?
Они этого не сделали. Вместо этого они сделали сегментирование видимым для программиста C, расширяя язык с помощью нескольких типов указателей: near
, far
и huge
. Указатель near
был только смещением, а указатели far
и huge
представляли собой комбинированный сегмент и смещение. Была опция компилятора для установки модели памяти , которая определяла, был ли тип указателя по умолчанию рядом или далеко.
В коде Windows, даже сегодня, вы часто увидите typedefs, например LPCSTR
(для const char*
). "LP" - это перерыв с 16-битных дней; это означает "длинный (дальний) указатель" .
Ответ 2
В настоящих 16-разрядных средах используются 16-битные указатели, которые достигают любого адреса. Примеры включают семейство PDP-11, 6800 (6802, 6809, 68HC11) и 8085. Это чистая и эффективная среда, как простая 32-разрядная архитектура.
Семейство 80x86 вынудило нас гибридное 16-битное/20-битное адресное пространство в так называемом "реальном режиме" - нативный адресный адрес 8086. Обычным механизмом борьбы с этим было усиление типов указателей на два основных типа: near
(16-разрядный указатель) и far
(32-разрядный указатель). Значение по умолчанию для указателей кода и данных может быть задано массово с помощью "модели памяти": tiny
, small
, compact
, medium
, far
и huge
(некоторые компиляторы не поддерживают все моделей).
Модель памяти tiny
полезна для небольших программ, в которых все пространство (код + данные + стек) меньше 64 КБ. Все указатели (по умолчанию) 16 бит или near
; указатель неявно связан с значением сегмента для всей программы.
Модель small
предполагает, что данные + стек меньше 64K и в том же сегменте; сегмент кода содержит только код, поэтому может иметь до 64 КБ, а максимальный объем памяти - 128 КБ. Указатели кода near
и неявно связаны с CS (сегмент кода). Указатели данных также near
и связаны с DS (сегмент данных).
Модель medium
имеет до 64 Кбайт данных + стек (например, маленький), но может иметь любое количество кода. Указатели данных имеют 16 бит и неявно привязаны к сегменту данных. Указатели кодов - это 32-битные указатели far
и имеют значение сегмента в зависимости от того, как компоновщик настроил группы кода (неудобная бухгалтерская справка).
Модель compact
представляет собой дополнение к среде: менее 64 Кбайт кода, но любой объем данных. Указатели данных far
, а указатели кода near
.
В модели large
или huge
подтип указателей по умолчанию - 32 бит или far
. Основное различие заключается в том, что огромные указатели всегда автоматически нормализуются, так что приращение их позволяет избежать проблем с оберткой 64 КБ. См. это.
Ответ 3
Модель памяти C никоим образом не подразумевает плоского адресного пространства. Это никогда не было. На самом деле спецификация языка C специально разработана для разрешения неплоских адресных пространств.
В самой тривиальной реализации с сегментированным адресным пространством размер самого большого непрерывного объекта будет ограничен размером сегмента (65536 байт на 16-битной платформе). Это означает, что size_t
в такой реализации будет 16 бит и что ваш код просто не будет компилироваться, так как вы пытаетесь объявить объект с большим размером, чем допустимый максимум.
Более сложная реализация будет поддерживать так называемую огромную модель памяти. Понятно, что проблем с непрерывными блоками памяти любого размера на сегментированной модели памяти действительно нет, для этого требуется лишь несколько дополнительных усилий в рисовании арифметики. Таким образом, в рамках огромной модели памяти реализация будет делать эти дополнительные усилия, что сделает код немного медленнее, но в то же время позволит обращаться к объектам практически любого размера. Таким образом, ваш код будет компилироваться отлично.
Ответ 4
В DOS 16 бит я не помню, чтобы это можно было сделать. Вы могли бы иметь несколько вещей, каждый из которых составлял 64 КБ (так как сегмент мог быть скорректирован, а смещение было обнулено), но не помню, можно ли пересечь границу с помощью одного массива. Плоское пространство памяти, в котором вы могли бы волей-неволей выделять все, что захотите, и достигнуть столь же глубокого, как вам понравилось, массива, не произошло, пока мы не сможем скомпилировать 32-битные DOS-программы (на 386 или 486 процессорах). Возможно, другие операционные системы и компиляторы, отличные от microsoft и borland, могут генерировать плоские массивы размером более 64 Кбайт. Win16 Я не помню эту свободу до тех пор, пока не ударит win32, возможно, моя память становится ржавой... В любом случае вам повезло или было богато иметь мегабайт памяти, 256-килобайтная или 512-килобайтная машина не была неслыханной. В конце концов, ваш флоппи-дисковод имел долю от мега до 1,44 мегабайта, а на вашем жестком диске, если таковой был, было дюжина или несколько мегабайт, так что вы просто не вычисляли то, что было так часто.
Я помню особую проблему, которую я узнал о DNS, когда вы могли загружать всю базу данных DNS всех зарегистрированных доменных имен на планете, на самом деле вам приходилось запускать собственный DNS-сервер, который был почти необходим в то время иметь веб-сайт. Этот файл был 35 мегабайт, а мой жесткий диск был 100 мегабайт, плюс dos и windows пережевывали некоторые из них. Вероятно, было 1 или 2 мегабайта памяти, возможно, в то время было возможно выполнять 32-битные программы dos. Часть, если бы я хотел проанализировать файл ascii, который я сделал за несколько проходов, но каждый проход должен был перейти в другой файл, и мне пришлось удалить предыдущий файл, чтобы на нем был место для следующего файла. Два дисковых контроллера на стандартной материнской плате, один для жесткого диска и один для диска cdrom, здесь снова этот материал не был дешевым, не было много запасных слотов isa, если вы могли позволить себе еще один жесткий диск и плату контроллера диска.
Была даже проблема чтения 64kbytes с C, вы пропустили fread количество байтов, которые вы хотели прочитать в 16-битном int, что означало от 0 до 65535 не 65536 байт, а производительность резко упала, если вы не читали даже размер секторов, поэтому вы просто читали 32 кбайта за раз, чтобы максимизировать производительность, 64k не доходило до дней dos32, когда вы окончательно убедились, что значение, переданное в fread, теперь было 32-битным числом, и компилятор не собирался отрубать верхние 16 бит и использовать только младшие 16 бит (что часто случалось, если вы использовали достаточное количество компиляторов/версий). В настоящее время мы сталкиваемся с аналогичными проблемами в переходе от 32 до 64, как и при переходе от 16 до 32 бит. Что наиболее интересно, так это код от таких людей, как я, которые узнали, что от 16 до 32 бит int изменен размер, но unsigned char и unsigned long этого не сделали, поэтому вы адаптировались и редко использовали int, чтобы ваши программы компилировались и работают как на 16, так и на 32 бит. (Кодекс от людей из этого поколения выделяется для других людей, которые также пережили его и использовали тот же трюк). Но для перехода от 32 до 64 это наоборот, а код, не рефакторизованный для использования объявлений типа uint32, страдает.
Чтение ответа wallyk, которое только что вошло, огромная вещь указателя, которая обернута вокруг, звонит в колокольчик, также не всегда умеет компилироваться для огромного. маленькая была модель с плоской памятью, с которой нам удобно сегодня, и с сегодняшнего дня было легко, потому что вам не приходилось беспокоиться о сегментах. Так что было бы желательно скомпилировать для маленьких, когда сможешь. У вас все еще не было много памяти или диска или дискового пространства, поэтому вы просто не имели дело с большими данными.
И, соглашаясь с другим ответом, сегмент смещения был 8088/8086 intel. Во всем мире еще не доминировали Intel, поэтому были другие платформы, которые просто имели плоское пространство памяти, или использовали другие трюки, возможно, в аппаратном обеспечении (вне процессора) для решения проблемы. Из-за того, что сегмент/смещение Intel смогло прокатиться на 16-битной вещи дольше, чем это, вероятно, должно было произойти. У сегмента/смещения были интересные и интересные вещи, которые вы могли бы с ним сделать, но это была такая же боль, как и все остальное. Вы либо упростили свою жизнь, либо жили в плоском пространстве памяти, либо постоянно беспокоились о границах сегментов.
Ответ 5
Действительно, фиксирование размера адреса на старом x86 является довольно сложным. Можно сказать, что его 16 бит, потому что арифметика, которую вы можете выполнить по адресу, должна вписываться в 16-битный регистр. Вы также можете сказать, что это 32 бит, потому что фактические адреса вычисляются против 16-битного общего назначения и 16-разрядного сегмента (все 32 бита значительны). Вы могли бы просто сказать это 20 бит, потому что регистры сегментов сдвинуты на 4 бита слева и добавлены в регистры gp для аппаратной адресации.
На самом деле это не так важно, какой из них вы выбрали, потому что все они примерно равны аппроксимации c абстрактной машины. Некоторые компиляторы позволяют выбрать модель памяти, которую вы использовали для каждой компиляции, в то время как другие просто предполагают 32-разрядные адреса, а затем тщательно проверяют, что операции, которые могут переполнять 16 бит, выдает инструкции, которые обрабатывают этот случай правильно.
Ответ 6
Отметьте эту запись в википедии. О дальних указателях. В принципе, его можно указать сегмент и смещение, что позволяет перейти к другому сегменту.