Что такое ближний, дальний и огромный указатели?

Может кто-нибудь объяснить мне эти указатели с подходящим примером... и когда эти указатели используются?

6 ответов

Решение

В старые времена, согласно руководству по Turbo C, ближний указатель составлял всего 16 бит, когда весь ваш код и данные помещались в один сегмент. Дальний указатель состоял из сегмента и смещения, но нормализация не проводилась. И огромный указатель был автоматически нормализован. Два дальних указателя могут предположительно указывать на одно и то же место в памяти, но быть разными, тогда как нормализованные огромные указатели, указывающие на одно и то же место в памяти, всегда будут равны.

Основным примером является архитектура Intel X86.

Внутри Intel 8086 был 16-разрядным процессором: все его регистры имели ширину 16 бит. Однако адресная шина имела ширину 20 бит (1 МиБ). Это означало, что вы не могли хранить весь адрес в реестре, ограничивая вас первыми 64 КБ.

Решением Intel было создание 16-разрядных "регистров сегментов", содержимое которых было бы смещено влево на четыре бита и добавлено к адресу. Например:

DS ("Data Segment") register:  1234 h
DX ("D eXtended") register:   + 5678h
                              ------
Actual address read:           179B8h

Это создало концепцию сегмента 64 КБ. Таким образом, "ближний" указатель будет просто содержимым регистра DX (5678h) и будет недействительным, если регистр DS уже не был установлен правильно, в то время как "дальний" указатель будет 32 бита (12345678h, DS с последующим DX) и будет всегда работать (но было медленнее, так как вам нужно было загрузить два регистра, а затем восстановить регистр DS, когда закончите).

(Как отмечает суперкадр ниже, смещение к переполненному DX "перевернется" перед добавлением в DS для получения окончательного адреса. Это позволило 16-разрядным смещениям получить доступ к любому адресу в сегменте 64 КБ, а не только к части, которая была ± 32 КБ от того места, куда указывал DX, как это делается в других архитектурах с 16-битной адресацией относительного смещения в некоторых инструкциях.)

Однако обратите внимание, что у вас может быть два "дальних" указателя, которые имеют разные значения, но указывают на один и тот же адрес. Например, дальний указатель 100079B8h указывает на то же место, что и 12345678h. Таким образом, сравнение указателей на дальних указателях было недопустимой операцией: указатели могут отличаться, но все же указывают на одно и то же место.

Именно здесь я решил, что Mac (с процессорами Motorola 68000 в то время) были не так уж и плохи, так что я упустил огромные указатели. IIRC, они были просто дальними указателями, которые гарантировали, что все перекрывающиеся биты в регистрах сегментов были 0, как во втором примере.

У Motorola не было этой проблемы с процессорами серии 6800, так как они были ограничены 64 КБ. Когда они создали архитектуру 68000, они перешли прямо к 32-битным регистрам и, таким образом, никогда не нуждались в ближних, дальних или огромных указателях., (Вместо этого их проблема заключалась в том, что на самом деле имели значение только 24 младших бита адреса, поэтому некоторые программисты (как известно, Apple) использовали старшие 8 бит в качестве "флагов указателя", что вызывало проблемы, когда шина адреса расширялась до 32 бит (4 ГиБ).)

Линус Торвальдс просто продержался до 80386 года, который предлагал "защищенный режим", где адреса были 32-битными, а регистры сегментов были старшей половиной адреса, и никакого добавления не требовалось, и с самого начала написал Linux для использования защищенного только в режиме, без странных сегментов, и поэтому у вас нет поддержки ближнего и дальнего указателя в Linux (и почему ни одна компания, разрабатывающая новую архитектуру, никогда не обратится к ним, если им нужна поддержка Linux). И они ели менестрелей Робина, и было много радости. (Ура...)

Разница между дальним и огромным указателями:

Как мы знаем, по умолчанию указатели near например: int *p это near указатель. Размер near указатель составляет 2 байта в случае 16-битного компилятора. И мы уже хорошо знаем, что размер компилятора варьируется; они хранят только смещение адреса, на который ссылается указатель. Адрес, состоящий только из смещения, имеет диапазон от 0 до 64 Кбайт.

Far а также huge указатели:

Far а также huge указатели имеют размер 4 байта. Они хранят как сегмент, так и смещение адреса, на который ссылается указатель. Тогда в чем разница между ними?

Ограничение дальнего указателя:

Мы не можем изменить или изменить адрес сегмента данного удаленного адреса, применив к нему любую арифметическую операцию. То есть с помощью арифметического оператора мы не можем перейти с одного сегмента на другой.

Если вы будете увеличивать дальний адрес за пределы максимального значения его адреса смещения вместо увеличения адреса сегмента, он будет повторять его адрес смещения в циклическом порядке. Это также называется упаковкой, т. Е. Если смещение 0xffff и мы добавляем 1, то это 0x0000 и аналогично, если мы уменьшаем 0x0000 на 1, то это 0xffff и помните, что нет никаких изменений в сегменте.

Теперь я собираюсь сравнить огромные и дальние указатели:

1. Когда дальний указатель увеличивается или уменьшается ТОЛЬКО, смещение указателя фактически увеличивается или уменьшается, но в случае большого указателя значение сегмента и смещения будет изменяться.

Рассмотрим следующий пример, взятый ЗДЕСЬ:

 int main()
    {
    char far* f=(char far*)0x0000ffff;
    printf("%Fp",f+0x1);
    return 0;
  }

тогда вывод:

0000:0000

Нет изменений в стоимости сегмента.

И в случае огромных указателей:

int main()
{
char huge* h=(char huge*)0x0000000f;
printf("%Fp",h+0x1);
return 0;
}

Выход:

0001:0000

Это происходит из-за того, что операция приращения изменяет не только значение смещения, но и значение сегмента. Это означает, что сегмент не изменится в случае far указатели, но в случае huge указатель, он может перемещаться из одного сегмента в другой.

2. Когда реляционные операторы используются в дальних указателях, сравниваются только смещения. Другими словами, реляционные операторы будут работать только в дальних указателях, если значения сегментов сравниваемых указателей совпадают. И в случае огромного это не произойдет, на самом деле происходит сравнение абсолютных адресов. Давайте разберемся с помощью примера far указатель:

int main()
{
char far * p=(char far*)0x12340001;
char far* p1=(char far*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}

Выход:

different

В huge указатель:

int main()
{
char huge * p=(char huge*)0x12340001;
char huge* p1=(char huge*)0x12300041;
if(p==p1)
printf("same");
else
printf("different");
return 0;
}

Выход:

same

Пояснение: Как мы видим абсолютный адрес для обоих p а также p1 является 12341 (1234*10+1 или же 1230*10+41) но они не считаются равными в 1-м случае, потому что в случае far сравниваются только смещения указателей, т.е. он будет проверять, 0001==0041, Что неверно.

А в случае огромных указателей операция сравнения выполняется на равных абсолютных адресах.

  1. Дальний указатель никогда не нормализуется, но huge указатель нормализован. Нормализованный указатель - это тот, который имеет максимально возможный адрес в сегменте, а это означает, что смещение никогда не превышает 15.

    предположим, если у нас есть 0x1234:1234 тогда нормализованная форма 0x1357:0004 (абсолютный адрес 13574). Огромный указатель нормализуется только тогда, когда с ним выполняется некоторая арифметическая операция, а не нормализуется во время присваивания.

     int main()
     {
      char huge* h=(char huge*)0x12341234;
      char huge* h1=(char huge*)0x12341234;
      printf("h=%Fp\nh1=%Fp",h,h1+0x1);
      return 0;
     }
    

    Выход:

    h=1234:1234
    
    h1=1357:0005
    

    Объяснение: huge указатель не нормализуется в случае присваивания. Но если над ним будет выполнена арифметическая операция, он будет нормализован. h является 1234:1234 а также h1 является 1357:0005 который нормализуется.

    4. Смещение огромного указателя меньше 16 из-за нормализации и не так в случае дальних указателей.

    Давайте возьмем пример, чтобы понять, что я хочу сказать:

     int main()
      {
      char far* f=(char far*)0x0000000f;
      printf("%Fp",f+0x1);
      return 0;
      }
    

Выход:

    0000:0010

В случае huge указатель:

      int main()
      {
      char huge* h=(char huge*)0x0000000f;
        printf("%Fp",h+0x1);
        return 0;
        }

        Output:
        0001:0000

Пояснение: поскольку мы увеличиваем дальний указатель на 1, это будет 0000:0010.И как мы увеличиваем огромный указатель на 1, то это будет 0001:0000 поскольку его смещение не может быть больше 15, другими словами, оно будет нормализовано.

Все, что содержится в этом ответе, относится только к старой модели сегментированной памяти 8086 и 80286.

рядом: 16-битный указатель, который может адресовать любой байт в сегменте 64 КБ

дальний: 32-битный указатель, который содержит сегмент и смещение. Обратите внимание, что поскольку сегменты могут перекрываться, два разных дальних указателя могут указывать на один и тот же адрес.

огромный: 32-битный указатель, в котором сегмент "нормализован", так что никакие два дальних указателя не указывают на один и тот же адрес, если они не имеют одинаковое значение.

чай: напиток с джемом и хлебом.

Это вернет нас в до, о, о, о, о

а когда эти указатели используются?

в 1980-х и 90-х годах, пока 32-битные Windows не стали повсеместными,

В некоторых архитектурах указатель, который может указывать на каждый объект в системе, будет больше и медленнее работать с указателем, который может указывать на полезное подмножество вещей. Многие люди дали ответы, связанные с 16-битной архитектурой x86. Различные типы указателей были распространены в 16-разрядных системах, хотя в 64-разрядных системах могут вновь появиться различия между опасениями и опасениями, в зависимости от того, как они реализованы (я не удивлюсь, если многие системы разработки перейдут на 64-разрядные указатели для все, несмотря на то, что во многих случаях это будет очень расточительно).

Во многих программах довольно легко поделить использование памяти на две категории: маленькие вещи, которые вместе составляют довольно небольшое количество материала (64 КБ или 4 ГБ), но будут доступны часто, и большие вещи, которые могут составить до гораздо большего количества, но к которым не нужно обращаться так часто. Когда приложение должно работать с частью объекта в области "больших вещей", оно копирует эту часть в область "мелких вещей", работает с ней и при необходимости записывает ее обратно.

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

(примечание: даже во многих 32-разрядных системах к определенным областям памяти можно обращаться напрямую без дополнительных инструкций, в то время как другие области не могут получить доступ. Если, например, на 68000 или ARM ведется регистр, указывающий на хранилище глобальных переменных, можно будет напрямую загрузить любую переменную в пределах первых 32 КБ (68000) или 2 КБ (ARM) этого регистра. Для извлечения переменной, хранящейся в другом месте, потребуется дополнительная инструкция для вычисления адреса. Размещение более часто используемых переменных в предпочтительных регионах и информирование компилятора позволит более эффективно генерировать код.

Эта терминология использовалась в 16-битных архитектурах.

В 16-битных системах данные были разделены на сегменты по 64 КБ. Каждый загружаемый модуль (программный файл, динамически загружаемая библиотека и т. Д.) Имел связанный сегмент данных, который мог хранить только до 64 КБ данных.

Указатель NEAR был указателем с 16-битной памятью и ссылался на данные (только) в текущем сегменте данных модулей.

16-битные программы, в которых требовалось более 64 КБ данных, могли обращаться к специальным распределителям, которые возвращали бы указатель FAR, который представлял собой идентификатор сегмента данных в старших 16 битах и ​​указатель на этот сегмент данных в младших 16 битах.

Тем не менее, более крупные программы хотели бы иметь дело с более чем 64 КБ непрерывных данных. ОГРОМНЫЙ указатель выглядит точно так же, как дальний указатель - он имеет 32-битное хранилище - но распределитель позаботился об упорядочении диапазона сегментов данных с последовательными идентификаторами, чтобы путем простого увеличения селектора сегмента данных можно было получить следующий фрагмент данных размером 64 КБ достиг.

Базовые стандарты языка C и C++ никогда официально не признавали эти концепции официально в своих моделях памяти - все указатели в программах на C или C++ должны иметь одинаковый размер. Таким образом, атрибуты NEAR, FAR и HUGE были расширениями, предоставляемыми различными поставщиками компиляторов.

Другие вопросы по тегам