Jump to content

Указатель (компьютерное программирование)

(Перенаправлено с Pointer (информатика) )

Я считаю, что операторы присваивания и переменные-указатели относятся к «самым ценным сокровищам» информатики.

Дональд Кнут , Структурное программирование, с переходом к Операторам [1]

Указатель a указывает на адрес памяти, связанный с переменной b, т. е. a содержит адрес памяти 1008 переменной b . На этой диаграмме вычислительная архитектура использует одно и то же адресное пространство и примитив данных как для указателей, так и для неуказателей; такой необходимости быть не должно.

В информатике указатель это объект во многих языках программирования , который хранит адрес памяти . Это может быть другое значение, расположенное в памяти компьютера , или, в некоторых случаях, значение отображаемого в памяти компьютерного оборудования . Указатель ссылается на место в памяти, и получение значения, хранящегося в этом месте, называется разыменованием указателя. По аналогии, номер страницы в указателе книги можно рассматривать как указатель на соответствующую страницу; разыменование такого указателя можно выполнить путем перехода к странице с заданным номером страницы и чтения текста, найденного на этой странице. Фактический формат и содержимое переменной-указателя зависят от базовой архитектуры компьютера .

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

Указатели также используются для хранения адресов точек входа для вызываемых подпрограмм в процедурном программировании и для связывания во время выполнения с библиотеками динамической компоновки (DLL) . В объектно-ориентированном программировании указатели на функции используются для привязки методов , часто с использованием таблиц виртуальных методов .

Указатель — это простая и более конкретная реализация более абстрактного ссылочного типа данных . Некоторые языки, особенно языки низкого уровня , поддерживают некоторые типы указателей, хотя некоторые имеют больше ограничений на их использование, чем другие. Хотя термин «указатель» использовался для ссылки на ссылки в целом, он более правильно применяется к структурам данных которых , интерфейс явно позволяет манипулировать указателем (арифметически через арифметика указателей ) в качестве адреса памяти, в отличие от волшебного файла cookie или возможности , которая этого не допускает. [ нужна ссылка ] Поскольку указатели допускают как защищенный, так и незащищенный доступ к адресам памяти , их использование сопряжено с риском, особенно в последнем случае. Примитивные указатели часто хранятся в формате, аналогичном целочисленному ; однако попытка разыменовать или «найти» такой указатель, значение которого не является допустимым адресом памяти, может привести к сбою программы (или содержать неверные данные). Чтобы облегчить эту потенциальную проблему, в целях безопасности типов указатели считаются отдельным типом, параметризованным типом данных, на которые они указывают, даже если базовое представление является целым числом. Также могут быть приняты другие меры (например, проверка достоверности и границ ), чтобы убедиться, что переменная-указатель содержит значение, которое одновременно является действительным адресом памяти и находится в пределах числового диапазона, к которому процессор способен обращаться.

В 1955 году советский украинский ученый-компьютерщик Екатерина Ющенко создала язык программирования «Адрес» , который сделал возможной косвенную адресацию и адреса высшего ранга – аналогичные указателям. Этот язык широко использовался на компьютерах Советского Союза. Однако за пределами Советского Союза он был неизвестен, и обычно Гарольду Лоусону . изобретение указки в 1964 году приписывают [2] В 2000 году Лоусон был удостоен награды IEEE в области компьютерных технологий « [f] за изобретение переменной-указателя и введение этой концепции в PL/I, таким образом впервые предоставив возможность гибко обрабатывать связанные списки в универсальной программе. язык высокого уровня». [3] Его основополагающая статья об этих концепциях появилась в июньском номере журнала CACM за 1967 год под названием «Обработка списков PL/I». Согласно Оксфордскому словарю английского языка , слово указатель впервые появилось в печати как указатель стека в техническом меморандуме Корпорации системного развития .

Формальное описание

[ редактировать ]

В информатике указатель — это своего рода ссылка .

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

Агрегат данных (или просто агрегат ) — это группа примитивов, которые логически смежны в памяти и которые рассматриваются коллективно как один элемент данных (например, агрегат может состоять из трех логически смежных байтов, значения которых представляют три координаты точка в пространстве). Когда агрегат полностью состоит из примитивов одного и того же типа, агрегат можно назвать массивом ; в некотором смысле примитив многобайтового слова представляет собой массив байтов, и некоторые программы используют слова таким образом.

Указатель — это концепция программирования, используемая в информатике для ссылки или указания на ячейку памяти, в которой хранится значение или объект. По сути, это переменная, которая хранит адрес памяти другой переменной или структуры данных, а не хранит сами данные.

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

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

Указатель памяти (или просто указатель ) — это примитив, значение которого предназначено для использования в качестве адреса памяти; Говорят, что указатель указывает на адрес памяти . Также говорят, что указатель указывает на данные [в памяти] , когда значение указателя является адресом данных в памяти.

В более общем смысле указатель — это своего рода ссылка , и говорят, что указатель ссылается на данные, хранящиеся где-то в памяти ; чтобы получить эти данные, нужно разыменовать указатель . Особенность, которая отличает указатели от других видов ссылок, заключается в том, что значение указателя интерпретируется как адрес памяти, что является концепцией довольно низкого уровня.

Ссылки служат уровнем косвенности: значение указателя определяет, какой адрес памяти (то есть какие данные) будет использоваться в вычислении. Поскольку косвенность является фундаментальным аспектом алгоритмов, указатели часто выражаются как фундаментальный тип данных в языках программирования ; в статически (или строго ) типизированных языках программирования тип указателя определяет тип данных, на которые указывает указатель.

Архитектурные корни

[ редактировать ]

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

В обычном случае указатель достаточно велик, чтобы вместить больше адресов, чем единиц памяти в системе. Это создает возможность того, что программа может попытаться получить доступ к адресу, который не соответствует ни одной единице памяти, либо потому, что установлено недостаточно памяти (т.е. за пределами диапазона доступной памяти), либо архитектура не поддерживает такие адреса. Первый случай на некоторых платформах, таких как архитектура Intel x86 , можно назвать ошибкой сегментации (segfault). Второй случай возможен в текущей реализации AMD64 , где указатели имеют длину 64 бита, а адреса расширяются только до 48 бит. Указатели должны соответствовать определенным правилам (каноническим адресам), поэтому при разыменовании неканонического указателя процессор выдает ошибку общей защиты .

С другой стороны, в некоторых системах единиц памяти больше, чем адресов. В этом случае используется более сложная схема, такая как сегментация памяти или подкачка, чтобы использовать разные части памяти в разное время. Последние воплощения архитектуры x86 поддерживают до 36 бит адресов физической памяти, которые были сопоставлены с 32-битным линейным адресным пространством посредством механизма подкачки PAE . Таким образом, одновременно можно получить доступ только к 1/16 возможного общего объема памяти. Другим примером того же семейства компьютеров был 16-битный защищенный режим процессора 80286 , который, хотя и поддерживал только 16 МБ физической памяти, мог получить доступ к 1 ГБ виртуальной памяти, но комбинация 16-битного адреса и сегмента регистры сделали доступ к более чем 64 КБ в одной структуре данных громоздким.

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

Использование

[ редактировать ]

Указатели напрямую поддерживаются без ограничений в таких языках, как PL/I , C , C++ , Pascal , FreeBASIC , а также неявно в большинстве языков ассемблера . Они в основном используются для создания ссылок , которые, в свою очередь, имеют основополагающее значение для построения почти всех структур данных , а также для передачи данных между различными частями программы.

В языках функционального программирования, которые в значительной степени полагаются на списки, ссылки на данные управляются абстрактно с помощью примитивных конструкций, таких как cons и соответствующих элементов car и cdr , которые можно рассматривать как специализированные указатели на первый и второй компоненты cons-ячейки. Это порождает некоторый идиоматический «аромат» функционального программирования. Структурируя данные в таких cons-списках , эти языки упрощают рекурсивные средства построения и обработки данных — например, путем рекурсивного доступа к элементам заголовка и хвоста списков списков; например, «забрать машину из CDR из CDR». Напротив, управление памятью, основанное на разыменовании указателей в некотором приближении к массиву адресов памяти, облегчает рассмотрение переменных как слотов, в которые данные могут быть назначены императивно .

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

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

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

Указатели C

[ редактировать ]

Основной синтаксис определения указателя: [4]

int *ptr;

Это заявляет ptr как идентификатор объекта следующего типа:

  • указатель, указывающий на объект типа int

Обычно это выражается более кратко: « ptr является указателем на int."

Поскольку язык C не определяет неявную инициализацию для объектов с автоматическим сроком хранения, [5] часто следует проявлять осторожность, чтобы гарантировать, что адрес, по которому ptr баллы действительны; вот почему иногда предлагается явно инициализировать указатель значением нулевого указателя , которое традиционно указывается в C с помощью стандартизированного макроса NULL: [6]

int *ptr = NULL;

Разыменование нулевого указателя в C приводит к неопределенному поведению . [7] что может иметь катастрофические последствия. Однако большинство реализаций [ нужна ссылка ] просто остановить выполнение рассматриваемой программы, обычно из-за ошибки сегментации .

Однако ненужная инициализация указателей может затруднить анализ программы и тем самым скрыть ошибки.

В любом случае, как только указатель объявлен, следующим логическим шагом будет его указание на что-то:

int a = 5;
int *ptr = NULL;

ptr = &a;

Это присваивает значение адреса a к ptr. Например, если a хранится в ячейке памяти 0x8130, затем значение ptr после присваивания будет 0x8130. Для разыменования указателя снова используется звездочка:

*ptr = 8;

Это означает, что нужно взять содержимое ptr (это 0x8130), «найдите» этот адрес в памяти и установите его значение 8. Если a позже будет снова использован, его новое значение будет 8.

Этот пример может быть более понятным, если исследовать память непосредственно. Предположим, что a находится по адресу 0x8130 в памяти и ptr по адресу 0x8134; также предположим, что это 32-битная машина, так что ширина int составляет 32 бита. Ниже показано, что будет в памяти после выполнения следующего фрагмента кода:

int a = 5;
int *ptr = NULL;
Адрес Содержание
0x8130 0x00000005
0x8134 0x00000000

(Здесь показан NULL-указатель 0x00000000.) Присвоив адрес a к ptr:

ptr = &a;

дает следующие значения памяти:

Адрес Содержание
0x8130 0x00000005
0x8134 0x00008130

Затем, разыменовав ptr по кодированию:

*ptr = 8;

компьютер возьмет содержимое ptr (это 0x8130), «найдите» этот адрес и присвойте этому местоположению 8, получив следующую память:

Адрес Содержание
0x8130 0x00000008
0x8134 0x00008130

Очевидно, что доступ a вернет значение 8, поскольку предыдущая инструкция изменила содержимое a с помощью указателя ptr.

Использование в структурах данных

[ редактировать ]

При настройке структур данных , таких как списки , очереди и деревья, необходимо иметь указатели, помогающие управлять тем, как структура реализуется и контролируется. Типичными примерами указателей являются указатели начала, указатели конца и указатели стека . Эти указатели могут быть либо абсолютными (фактический физический адрес или виртуальный адрес в виртуальной памяти ), либо относительными ( смещение от абсолютного начального адреса («базового»), которое обычно использует меньше битов, чем полный адрес, но обычно требует одного дополнительного указателя. арифметическая операция для решения).

Относительные адреса представляют собой форму ручной сегментации памяти и имеют многие ее преимущества и недостатки. Двухбайтовое смещение, содержащее 16-битное целое число без знака, можно использовать для обеспечения относительной адресации размером до 64 КиБ (2 16 байты) структуры данных. Его можно легко расширить до 128, 256 или 512 КиБ, если указанный адрес принудительно выровнен по границе полуслова, слова или двойного слова (но, требуя дополнительной побитовой операции «сдвига влево» — на 1, 2 или 3 бита — чтобы скорректировать смещение в 2, 4 или 8 раз перед его добавлением к базовому адресу). Однако в целом такие схемы доставляют много хлопот, и для удобства программиста абсолютные адреса (и лежащее в их основе плоское адресное пространство предпочтительны ).

Смещение в один байт, такое как шестнадцатеричное ASCII значение символа (например, X'29'), может использоваться для указания на альтернативное целочисленное значение (или индекс) в массиве (например, X'01'). Таким образом, символы могут быть очень эффективно преобразованы из « необработанных данных » в пригодный для использования последовательный индекс , а затем в абсолютный адрес без таблицы поиска .

Массивы C

[ редактировать ]

В C индексация массива формально определяется в терминах арифметики указателей; то есть спецификация языка требует, чтобы array[i] быть эквивалентным *(array + i). [8] Таким образом, в C массивы можно рассматривать как указатели на последовательные области памяти (без пробелов). [8] и синтаксис доступа к массивам идентичен синтаксису, который можно использовать для разыменования указателей. Например, массив array может быть объявлен и использован следующим образом:

int array[5];      /* Declares 5 contiguous integers */
int *ptr = array;  /* Arrays can be used as pointers */
ptr[0] = 1;        /* Pointers can be indexed with array syntax */
*(array + 1) = 2;  /* Arrays can be dereferenced with pointer syntax */
*(1 + array) = 2;  /* Pointer addition is commutative */
2[array] = 4;      /* Subscript operator is commutative */

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

Хотя большинство операторов для массивов и указателей эквивалентны, результат sizeof оператор отличается. В этом примере sizeof(array) будет оценивать 5*sizeof(int) (размер массива), а sizeof(ptr) будет оценивать sizeof(int*), размер самого указателя.

Значения массива по умолчанию могут быть объявлены следующим образом:

int array[5] = {2, 4, 3, 1, 5};

Если array находится в памяти, начиная с адреса 0x1000 на 32-битной машине с прямым порядком байтов, то память будет содержать следующее (значения указаны в шестнадцатеричном формате , как и адреса):

0 1 2 3
1000 2 0 0 0
1004 4 0 0 0
1008 3 0 0 0
100С 1 0 0 0
1010 5 0 0 0

Здесь представлены пять целых чисел: 2, 4, 3, 1 и 5. Эти пять целых чисел занимают 32 бита (4 байта), каждое из которых наименее значащий байт хранится первым (это архитектура ЦП с прямым порядком байтов ) и сохраняются последовательно. начиная с адреса 0x1000.

Синтаксис C с указателями:

  • array означает 0x1000;
  • array + 1 означает 0x1004: «+ 1» означает добавление размера 1 int, что составляет 4 байта;
  • *array означает разыменование содержимого array. Рассматривая содержимое как адрес памяти (0x1000), найдите значение в этом месте (0x0002);
  • array[i] означает номер элемента i, отсчет от 0, из array который переводится на *(array + i).

Последний пример — как получить доступ к содержимому array. Разбираем это:

  • array + i это ячейка памяти (i) й элемент array, начиная с i=0;
  • *(array + i) берет этот адрес памяти и разыменовывает его для доступа к значению.

C связанный список

[ редактировать ]

Ниже приведен пример определения связанного списка в C.

/* the empty linked list is represented by NULL
 * or some other sentinel value */
#define EMPTY_LIST  NULL

struct link {
    void        *data;  /* data of this link */
    struct link *next;  /* next link; EMPTY_LIST if there is none */
};

Это определение рекурсивного указателя по существу такое же, как определение рекурсивного указателя из языка программирования Haskell :

 data Link a = Nil
             | Cons a (Link a)

Nil пустой список, и Cons a (Link a) это минус -ячейка типа a с другой ссылкой также типа a.

Однако определение со ссылками проверено по типу и не использует потенциально запутанные значения сигналов. По этой причине структуры данных в C обычно обрабатываются через функции-обертки , которые тщательно проверяются на корректность.

Передача по адресу с использованием указателей

[ редактировать ]

Указатели можно использовать для передачи переменных по их адресу, что позволяет изменять их значение. Например, рассмотрим следующий код C :

/* a copy of the int n can be changed within the function without affecting the calling code */
void passByValue(int n) {
    n = 12;
}

/* a pointer m is passed instead. No copy of the value pointed to by m is created */
void passByAddress(int *m) {
    *m = 14;
}

int main(void) {
    int x = 3;

    /* pass a copy of x's value as the argument */
    passByValue(x);
    // the value was changed inside the function, but x is still 3 from here on

    /* pass x's address as the argument */
    passByAddress(&x);
    // x was actually changed by the function and is now equal to 14 here

    return 0;
}

Динамическое распределение памяти

[ редактировать ]

В некоторых программах необходимый объем памяти зависит от того, что может ввести пользователь . В таких случаях программисту необходимо динамически распределять память. Это делается путем выделения памяти в куче, а не в стеке , где обычно хранятся переменные (хотя переменные также могут храниться в регистрах ЦП). Динамическое выделение памяти может осуществляться только с помощью указателей, а имена, как и в случае с общими переменными, не могут быть заданы.

Указатели используются для хранения и управления адресами динамически выделяемых блоков памяти. Такие блоки используются для хранения объектов данных или массивов объектов. Большинство структурированных и объектно-ориентированных языков предоставляют область памяти, называемую кучей или свободным хранилищем , из которой объекты динамически выделяются.

Пример кода C ниже иллюстрирует, как объекты структуры динамически выделяются и на них ссылаются. Стандартная библиотека C предоставляет функцию malloc() для выделения блоков памяти из кучи. Он принимает размер выделяемого объекта в качестве параметра и возвращает указатель на вновь выделенный блок памяти, подходящий для хранения объекта, или возвращает нулевой указатель, если выделение не удалось.

/* Parts inventory item */
struct Item {
    int         id;     /* Part number */
    char *      name;   /* Part name   */
    float       cost;   /* Cost        */
};

/* Allocate and initialize a new Item object */
struct Item * make_item(const char *name) {
    struct Item * item;

    /* Allocate a block of memory for a new Item object */
    item = malloc(sizeof(struct Item));
    if (item == NULL)
        return NULL;

    /* Initialize the members of the new Item */
    memset(item, 0, sizeof(struct Item));
    item->id =   -1;
    item->name = NULL;
    item->cost = 0.0;

    /* Save a copy of the name in the new Item */
    item->name = malloc(strlen(name) + 1);
    if (item->name == NULL) {
        free(item);
        return NULL;
    }
    strcpy(item->name, name);

    /* Return the newly created Item object */
    return item;
}

Код ниже иллюстрирует, как объекты памяти динамически освобождаются, т. е. возвращаются в кучу или свободное хранилище. Стандартная библиотека C предоставляет функцию free() для освобождения ранее выделенного блока памяти и возврата его обратно в кучу.

/* Deallocate an Item object */
void destroy_item(struct Item *item) {
    /* Check for a null object pointer */
    if (item == NULL)
        return;

    /* Deallocate the name string saved within the Item */
    if (item->name != NULL) {
        free(item->name);
        item->name = NULL;
    }

    /* Deallocate the Item object itself */
    free(item);
}

Аппаратное обеспечение с отображением памяти

[ редактировать ]

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

Присвоение адресов указателям — бесценный инструмент при программировании микроконтроллеров . Ниже приведен простой пример объявления указателя типа int и инициализации его шестнадцатеричным адресом, в этом примере константой 0x7FFF:

int *hardware_address = (int *)0x7FFF;

В середине 80-х использование BIOS для доступа к видеовозможностям ПК было медленным. Приложения с интенсивным отображением обычно использовались для CGA прямого доступа к видеопамяти путем приведения шестнадцатеричной константы 0xB8000 к указателю на массив из 80 беззнаковых 16-битных целых значений. Каждое значение состояло из кода ASCII в младшем байте и цвета в старшем байте. Таким образом, чтобы поместить букву «А» в строке 5 столбца 2 ярко-белым цветом на синем, нужно было бы написать следующий код:

#define VID ((unsigned short (*)[80])0xB8000)

void foo(void) {
    VID[4][1] = 0x1F00 | 'A';
}

Использование в контрольных таблицах

[ редактировать ]

Управляющие таблицы , используемые для управления ходом выполнения программы, обычно широко используют указатели. Указатели, обычно встроенные в запись таблицы, могут, например, использоваться для хранения точек входа в подпрограммы , которые должны выполняться на основе определенных условий, определенных в той же записи таблицы. Однако указатели могут быть просто индексами на другие отдельные, но связанные таблицы, содержащие массив фактических адресов или сами адреса (в зависимости от доступных конструкций языка программирования). Их также можно использовать для указания на более ранние записи таблицы (как при обработке цикла) или вперед для пропуска некоторых записей таблицы (как при переключении или «раннем» выходе из цикла). Для этой последней цели «указатель» может быть просто номером записи таблицы и может быть преобразован в фактический адрес с помощью простой арифметики.

Типизированные указатели и приведение типов

[ редактировать ]

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

Например, в С

int *money;
char *bags;

money будет целочисленным указателем и bags будет указатель на символ. Следующее приведет к предупреждению компилятора о «назначении из несовместимого типа указателя» в GCC.

bags = money;

потому что money и bags были объявлены с разными типами. Чтобы подавить предупреждение компилятора, необходимо явно указать, что вы действительно хотите выполнить присвоение, приведя к типу его .

bags = (char *)money;

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

Проект стандарта C 2005 года требует, чтобы приведение указателя, полученного из одного типа, к одному из другого типа, сохраняло правильность выравнивания для обоих типов (6.3.2.3 Указатели, пар. 7): [9]

char *external_buffer = "abcdef";
int *internal_data;

internal_data = (int *)external_buffer;  // UNDEFINED BEHAVIOUR if "the resulting pointer
                                         // is not correctly aligned"

В языках, допускающих арифметику с указателями, арифметика с указателями учитывает размер типа. Например, добавление целого числа к указателю создает другой указатель, указывающий на адрес, который в это число раз превышает размер типа. Это позволяет нам легко вычислить адреса элементов массива заданного типа, как было показано в примере с массивами C выше. Когда указатель одного типа приводится к другому типу другого размера, программист должен ожидать, что арифметика указателя будет рассчитываться по-другому. В C, например, если money массив начинается с 0x2000 и sizeof(int) составляет 4 байта, тогда как sizeof(char) составляет 1 байт, то money + 1 будет указывать на 0x2004, но bags + 1 будет указывать на 0x2001. Другие риски приведения включают потерю данных, когда «широкие» данные записываются в «узкие» места (например, bags[0] = 65537;), неожиданные результаты при сдвиге значений битов и проблемы сравнения, особенно со знаковыми и беззнаковыми значениями.

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

Значение указателей

[ редактировать ]

В C и C++ даже если два указателя сравниваются как равные, это не означает, что они эквивалентны. В этих языках и LLVM это правило интерпретируется как означающее, что «тот факт, что два указателя указывают на один и тот же адрес, не означает, что они равны в том смысле, что их можно использовать взаимозаменяемо», разница между указателями называется их происхождение . [10] Приведение к целочисленному типу, например uintptr_t определяется реализацией, и сравнение, которое оно обеспечивает, не дает большего понимания того, являются ли эти два указателя взаимозаменяемыми. Кроме того, дальнейшее преобразование в байты и арифметику отпугнет оптимизаторов, пытающихся отслеживать использование указателей, и эта проблема все еще выясняется в академических исследованиях. [11]

Делаем указатели безопаснее

[ редактировать ]

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

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

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

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

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

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

Специальные виды указателей

[ редактировать ]

Виды, определяемые по значению

[ редактировать ]

Нулевой указатель

[ редактировать ]

Нулевой указатель имеет значение, зарезервированное для указания того, что указатель не ссылается на действительный объект. Нулевые указатели обычно используются для представления таких условий, как конец списка неизвестной длины или невозможность выполнить какое-либо действие; такое использование нулевых указателей можно сравнить с типами, допускающими значение NULL , и со значением Nothing в типе параметра .

Висячий указатель

[ редактировать ]

Висячий указатель это указатель, который не указывает на действительный объект и, следовательно, может привести к сбою программы или вести себя странно. В Pascal или языках программирования C указатели, которые специально не инициализируются, могут указывать на непредсказуемые адреса в памяти.

В следующем примере кода показан висячий указатель:

int func(void) {
    char *p1 = malloc(sizeof(char)); /* (undefined) value of some place on the heap */
    char *p2;       /* dangling (uninitialized) pointer */
    *p1 = 'a';      /* This is OK, assuming malloc() has not returned NULL. */
    *p2 = 'b';      /* This invokes undefined behavior */
}

Здесь, p2 может указывать на любое место в памяти, поэтому выполнение присваивания *p2 = 'b'; может повредить неизвестную область памяти или вызвать ошибку сегментации .

Дикая ветка

[ редактировать ]

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

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

Виды, определяемые структурой

[ редактировать ]

Авторелятивный указатель

[ редактировать ]

Авторелятивный указатель — это указатель, значение которого интерпретируется как смещение от адреса самого указателя; таким образом, если структура данных имеет элемент автоотносительного указателя, который указывает на некоторую часть самой структуры данных, то структура данных может быть перемещена в памяти без необходимости обновлять значение автоотносительного указателя. [12]

В цитируемом патенте также используется термин «относительный указатель», означающий то же самое. Однако значение этого термина использовалось и по-другому:

  • означать смещение от адреса структуры, а не от адреса самого указателя; [ нужна ссылка ]
  • означает указатель, содержащий свой собственный адрес, который может быть полезен для восстановления в любой произвольной области памяти набора структур данных, указывающих друг на друга. [13]

Базовый указатель

[ редактировать ]

Базовый указатель это указатель, значение которого является смещением от значения другого указателя. Это можно использовать для хранения и загрузки блоков данных, присваивая адрес начала блока базовому указателю. [14]

Виды, определяемые использованием или типом данных

[ редактировать ]

Множественная косвенность

[ редактировать ]

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

struct element {
    struct element *next;
    int            value;
};

struct element *head = NULL;

Эта реализация использует указатель на первый элемент списка в качестве заместителя всего списка. Если в начало списка добавляется новое значение, head необходимо изменить, чтобы он указывал на новый элемент. Поскольку аргументы C всегда передаются по значению, использование двойной косвенности позволяет правильно реализовать вставку и имеет желаемый побочный эффект в виде исключения кода специального случая для работы с вставками в начале списка:

// Given a sorted list at *head, insert the element item at the first
// location where all earlier elements have lesser or equal value.
void insert(struct element **head, struct element *item) {
    struct element **p;  // p points to a pointer to an element
    for (p = head; *p != NULL; p = &(*p)->next) {
        if (item->value <= (*p)->value)
            break;
    }
    item->next = *p;
    *p = item;
}

// Caller does this:
insert(&head, item);

В этом случае, если значение item меньше, чем у head, звонивший head корректно обновляется по адресу нового элемента.

Базовый пример — argv аргумент основной функции в C (и C++) , который в прототипе задается как char **argv— это потому, что переменная argv сам по себе является указателем на массив строк (массив массивов), поэтому *argv является указателем на 0-ю строку (по соглашению имя программы) и **argv — это 0-й символ 0-й строки.

Указатель функции

[ редактировать ]

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

int sum(int n1, int n2) {   // Function with two integer parameters returning an integer value
    return n1 + n2;
}

int main(void) {
    int a, b, x, y;
    int (*fp)(int, int);    // Function pointer which can point to a function like sum
    fp = &sum;              // fp now points to function sum
    x = (*fp)(a, b);        // Calls function sum with arguments a and b
    y = sum(a, b);          // Calls function sum with arguments a and b
}

Назад указатель

[ редактировать ]

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

Моделирование с использованием индекса массива

[ редактировать ]

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

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

Теоретически возможно даже, используя описанную выше технику вместе с подходящим симулятором набора команд , смоделировать любой машинный код или промежуточный ( байтовый код ) любого процессора/языка на другом языке, который вообще не поддерживает указатели (например, Java / JavaScript ). Для достижения этой цели двоичный код изначально может быть загружен в смежные байты массива, чтобы симулятор мог «читать», интерпретировать и действовать полностью в памяти, содержащейся в том же массиве. При необходимости, чтобы полностью избежать проблем с переполнением буфера , проверку границ обычно можно выполнить для компилятора (или, если нет, вручную запрограммировать в симуляторе).

Поддержка на различных языках программирования

[ редактировать ]

Ада — строго типизированный язык, в котором все указатели типизированы и разрешены только безопасные преобразования типов. Все указатели по умолчанию инициализируются nullи любая попытка доступа к данным через null указатель вызывает исключения возникновение . Указатели в Ada называются типами доступа . Ada 83 не разрешала арифметику по типам доступа (хотя многие поставщики компиляторов предусматривали это как нестандартную функцию), но Ada 95 поддерживает «безопасную» арифметику по типам доступа через пакет System.Storage_Elements.

Несколько старых версий BASIC для платформы Windows поддерживали функцию STRPTR() для возврата адреса строки и функцию VARPTR() для возврата адреса переменной. Visual Basic 5 также поддерживал OBJPTR() для возврата адреса интерфейса объекта и оператор ADDRESSOF для возврата адреса функции. Типы всех них — целые числа, но их значения эквивалентны значениям, хранящимся в типах указателей.

Однако новые диалекты BASIC , такие как FreeBASIC или BlitzMax , имеют исчерпывающую реализацию указателей. В FreeBASIC арифметика ANY указатели (эквивалент C void*) рассматриваются так, как если бы ANY указатель имел ширину в байтах. ANY указатели не могут быть разыменованы, как в C. Кроме того, приведение между ANY и указатели любого другого типа не будут генерировать никаких предупреждений.

dim as integer f = 257
dim as any ptr g = @f
dim as integer ptr i = g
assert(*i = 257)
assert( (g + 4) = (@f + 1) )

В C и C++ указатели — это переменные, в которых хранятся адреса, и они могут иметь значение null . Каждый указатель имеет тип, на который он указывает, но можно свободно выполнять преобразование между типами указателей (но не между указателем функции и указателем объекта). Специальный тип указателя, называемый «void pointer», позволяет указывать на любой (нефункциональный) объект, но ограничен тем фактом, что его нельзя разыменовать напрямую (он должен быть приведен). Сам адрес часто можно напрямую манипулировать путем приведения указателя к целочисленному типу достаточного размера и обратно, хотя результаты определяются реализацией и действительно могут вызывать неопределенное поведение; в то время как более ранние стандарты C не имели целочисленного типа, который гарантированно был бы достаточно большим, C99 определяет uintptr_t typedef, имя определенное в <stdint.h>, но реализация не обязательно должна это обеспечивать.

C++ полностью поддерживает указатели C и приведение типов C. Он также поддерживает новую группу операторов приведения типов, помогающую отловить некоторые непреднамеренные опасные приведения во время компиляции. Начиная с C++11 , стандартная библиотека C++ также предоставляет интеллектуальные указатели ( unique_ptr, shared_ptr и weak_ptr), которые в некоторых ситуациях можно использовать как более безопасную альтернативу примитивным указателям C. C++ также поддерживает другую форму ссылки, совершенно отличную от указателя, называемую просто ссылкой или ссылочным типом .

Арифметика указателя , то есть возможность изменять целевой адрес указателя с помощью арифметических операций (а также сравнения величин), ограничена стандартом языка, чтобы оставаться в пределах одного объекта массива (или сразу после него) и будет в противном случае вызовите неопределенное поведение . Добавление или вычитание указателя перемещает его на величину, кратную размеру его типа данных . Например, добавление 1 к указателю на 4-байтовые целочисленные значения увеличит адрес байта, на который указывает указатель, на 4. Это приводит к увеличению указателя до следующего элемента в непрерывном массиве целых чисел, что часто желаемый результат. Арифметика указателей не может быть выполнена на void указатели, поскольку тип void не имеет размера, и, следовательно, к указанному адресу нельзя добавить, хотя gcc и другие компиляторы будут выполнять арифметику байтов на void* как нестандартное расширение, рассматривая его так, как если бы оно было char *.

Арифметика указателей предоставляет программисту единый способ работы с различными типами: добавление и вычитание количества требуемых элементов вместо фактического смещения в байтах. (Арифметика указателей с char * указатели используют смещения байтов, потому что sizeof(char) по определению равен 1.) В частности, определение C явно заявляет, что синтаксис a[n], который является n-й элемент массива a, эквивалентно *(a + n), который является содержимым элемента, на который указывает a + n. Это означает, что n[a] эквивалентно a[n], и можно написать, например, a[3] или 3[a] одинаково хорошо для доступа к четвертому элементу массива a.

Несмотря на свою эффективность, арифметика указателей может стать источником компьютерных ошибок . Это имеет тенденцию сбивать с толку начинающих программистов , заставляя их работать в разных контекстах: выражение может быть обычным арифметическим или арифметическим с указателями, и иногда легко перепутать одно с другим. В ответ на это многие современные языки программирования высокого уровня (например, Java ) не разрешают прямой доступ к памяти с использованием адресов. Кроме того, безопасный диалект C Cyclone решает многие проблемы с указателями. см . в разделе Язык программирования C. Дополнительную информацию

The void указатель или void*, поддерживается в ANSI C и C++ как универсальный тип указателя. Указатель на void может хранить адрес любого объекта (не функции), [а] и в C неявно преобразуется в любой другой тип указателя объекта при присвоении, но в случае разыменования он должен быть приведен явно. K&R C б/у char* для цели «независимого от типа указателя» (до ANSI C).

int x = 4;
void* p1 = &x;
int* p2 = p1;       // void* implicitly converted to int*: valid C, but not C++
int a = *p2;
int b = *(int*)p1;  // when dereferencing inline, there is no implicit conversion

C++ не позволяет неявное преобразование void* к другим типам указателей, даже в присваиваниях. Это было дизайнерское решение, позволяющее избежать небрежного и даже непреднамеренного приведения типов, хотя большинство компиляторов выводят только предупреждения, а не ошибки, при обнаружении других приведений.

int x = 4;
void* p1 = &x;
int* p2 = p1;                     // this fails in C++: there is no implicit conversion from void*
int* p3 = (int*)p1;               // C-style cast
int* p4 = reinterpret_cast<int*>(p1);  // C++ cast

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

Указатель на член

[ редактировать ]

В C++ можно определить указатели на нестатические члены класса. Если класс C имеет члена T a затем &C::a является указателем на член a типа T C::*. Этот член может быть объектом или функцией . [16] Их можно использовать справа от операторов. .* и ->* для доступа к соответствующему члену.

struct S {
int a;
int f() const {return a;}
};
S s1{};
S* ptrS = &s1;

int S::* ptr = &S::a; // pointer to S::a
int (S::* fp)()const = &S::f; // pointer to S::f

s1.*ptr = 1;
std::cout << (s1.*fp)() << "\n"; // prints 1
ptrS->*ptr = 2;
std::cout << (ptrS->*fp)() << "\n"; // prints 2

Обзор синтаксиса объявления указателя

[ редактировать ]

Эти объявления указателей охватывают большинство вариантов объявлений указателей. Конечно, возможно иметь тройные указатели, но основные принципы тройного указателя уже существуют в двойном указателе. Используемое здесь именование соответствует выражению typeid(type).name() равно для каждого из этих типов при использовании g++ или clang . [17] [18]

char A5_A5_c [5][5];   /* array of arrays of chars */
char *A5_Pc [5];       /* array of pointers to chars */
char **PPc;            /* pointer to pointer to char ("double pointer") */
char (*PA5_c) [5];     /* pointer to array(s) of chars */
char *FPcvE();         /* function which returns a pointer to char(s) */
char (*PFcvE)();       /* pointer to a function which returns a char */
char (*FPA5_cvE())[5]; /* function which returns pointer to an array of chars */
char (*A5_PFcvE[5])(); /* an array of pointers to functions which return a char */

Следующие объявления, включающие указатели на члены, допустимы только в C++:

class C;
class D;
char C::* M1Cc;              /* pointer-to-member to char */
char C::*A5_M1Cc [5];        /* array of pointers-to-member to char */
char* C::* M1CPc;            /* pointer-to-member to pointer to char(s) */
char C::** PM1Cc;            /* pointer to pointer-to-member to char */
char (*M1CA5_c) [5];         /* pointer-to-member to array(s) of chars */
char C::* FM1CcvE();         /* function which returns a pointer-to-member to char */
char D::* C::* M1CM1Dc;      /* pointer-to-member to pointer-to-member to pointer to char(s) */
char C::* C::* M1CMS_c;      /* pointer-to-member to pointer-to-member to pointer to char(s) */
char (C::* FM1CA5_cvE())[5]; /* function which returns pointer-to-member to an array of chars */
char (C::* M1CFcvE)()        /* pointer-to-member-function which returns a char */
char (C::* A5_M1CFcvE[5])(); /* an array of pointers-to-member-functions which return a char */

The () и [] имеют более высокий приоритет, чем *. [19]

В языке программирования C# указатели поддерживаются либо путем маркировки блоков кода, содержащих указатели, с помощью unsafe ключевое слово или по using тот System.Runtime.CompilerServices положения сборки для доступа к указателю. Синтаксис по существу такой же, как и в C++, а указанный адрес может быть либо управляемой , либо неуправляемой памятью. Однако указатели на управляемую память (любой указатель на управляемый объект) должны быть объявлены с использованием метода fixed Ключевое слово, которое не позволяет сборщику мусора перемещать указанный объект в рамках управления памятью, пока указатель находится в области видимости, тем самым сохраняя действительный адрес указателя.

Однако исключением из этого правила является использование IntPtr структуру, которая представляет собой управляемый памятью эквивалент int*и не требует unsafe ключевое слово, ни CompilerServices сборка. Этот тип часто возвращается при использовании методов из класса System.Runtime.InteropServices, например:

// Get 16 bytes of memory from the process's unmanaged memory
IntPtr pointer = System.Runtime.InteropServices.Marshal.AllocHGlobal(16);

// Do something with the allocated memory

// Free the allocated memory
System.Runtime.InteropServices.Marshal.FreeHGlobal(pointer);

Платформа .NET включает в себя множество классов и методов. System и System.Runtime.InteropServices пространства имен (например, Marshal класс), которые преобразуют типы .NET (например, System.String) ко многим неуправляемым типам и указателям и обратно (например, LPWSTR или void*), чтобы разрешить связь с неуправляемым кодом . Большинство таких методов имеют те же требования к разрешениям безопасности, что и неуправляемый код, поскольку они могут влиять на произвольные места в памяти.

Язык программирования COBOL поддерживает указатели на переменные. Примитивные или групповые (записные) объекты данных, объявленные внутри LINKAGE SECTION программы по своей сути основаны на указателях, где единственная память, выделяемая в программе, - это место для адреса элемента данных (обычно одно слово памяти). В исходном коде программы эти элементы данных используются так же, как и любые другие. WORKING-STORAGE переменная, но к их содержимому осуществляется неявный доступ косвенно через их LINKAGE указатели.

Пространство памяти для каждого указываемого объекта данных обычно выделяется динамически с использованием внешних CALL операторов или через встроенные расширенные языковые конструкции, такие как EXEC CICS или EXEC SQL заявления.

Расширенные версии COBOL также предоставляют переменные указателя, объявленные с помощью USAGE IS POINTER статьи. Значения таких переменных-указателей устанавливаются и изменяются с помощью SET и SET ADDRESS заявления.

Некоторые расширенные версии COBOL также предоставляют PROCEDURE-POINTER переменные, способные хранить адреса исполняемого кода .

Язык PL/I обеспечивает полную поддержку указателей на все типы данных (включая указатели на структуры), рекурсию , многозадачность , обработку строк и обширные встроенные функции . PL/I был настоящим шагом вперед по сравнению с языками программирования того времени. [ нужна ссылка ] Указатели PL/I не являются типизированными, поэтому для разыменования или присвоения указателей приведение типов не требуется. Синтаксис объявления указателя: DECLARE xxx POINTER;, который объявляет указатель с именем «xxx». Указатели используются с BASED переменные. Базовую переменную можно объявить с помощью локатора по умолчанию ( DECLARE xxx BASED(ppp); или без ( DECLARE xxx BASED;), где xxx — базовая переменная, которая может быть переменной-элементом, структурой или массивом, а ppp — указатель по умолчанию). Такая переменная может быть адресована без явной ссылки на указатель ( xxx=1;, или может быть адресован с явной ссылкой на локатор по умолчанию (ppp) или на любой другой указатель ( qqq->xxx=1;).

Арифметика указателей не является частью стандарта PL/I, но многие компиляторы допускают выражения вида ptr = ptr±expression. IBM PL/I также имеет встроенную функцию PTRADD выполнить арифметику. Арифметика указателей всегда выполняется в байтах.

Компиляторы IBM Enterprise PL/I имеют новую форму типизированного указателя, называемую HANDLE.

Язык программирования D является производным от C и C++, который полностью поддерживает указатели C и приведение типов C.

Эйфелева

[ редактировать ]

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

В Фортране-90 появилась возможность строго типизированного указателя. Указатели Фортрана содержат больше, чем просто адрес памяти. Они также инкапсулируют нижнюю и верхнюю границы размеров массива, шаги (например, для поддержки произвольных разделов массива) и другие метаданные. ассоциации Оператор , => используется для того, чтобы связать POINTER к переменной, которая имеет TARGET атрибут. Фортран-90 ALLOCATE Оператор также может использоваться для связывания указателя с блоком памяти. Например, следующий код может использоваться для определения и создания структуры связанного списка:

type real_list_t
  real :: sample_data(100)
  type (real_list_t), pointer :: next => null ()
end type

type (real_list_t), target :: my_real_list
type (real_list_t), pointer :: real_list_temp

real_list_temp => my_real_list
do
  read (1,iostat=ioerr) real_list_temp%sample_data
  if (ioerr /= 0) exit
  allocate (real_list_temp%next)
  real_list_temp => real_list_temp%next
end do

В Fortran-2003 добавлена ​​поддержка указателей процедур. Кроме того, в рамках функции взаимодействия C Fortran-2003 поддерживает встроенные функции для преобразования указателей в стиле C в указатели Fortran и обратно.

В Go есть указатели. Синтаксис его объявления эквивалентен синтаксису C, но написан наоборот и заканчивается типом. В отличие от C, Go имеет сборку мусора и запрещает арифметику указателей. Ссылочные типы, как и в C++, не существуют. Некоторые встроенные типы, такие как карты и каналы, заключены в рамку (т. е. внутри они являются указателями на изменяемые структуры) и инициализируются с помощью make функция. В подходе к унифицированному синтаксису указателей и неуказателей стрелка ( ->) удален: оператор точки в указателе относится к полю или методу разыменованного объекта. Однако это работает только с 1 уровнем косвенности.

нет явного представления указателей В Java . Вместо этого более сложные структуры данных, такие как объекты и массивы, реализуются с использованием ссылок . Язык не предоставляет каких-либо явных операторов манипулирования указателями. Однако код по-прежнему может попытаться разыменовать нулевую ссылку (нулевой указатель), что приведет к возникновению исключения во время выполнения. Пространство, занимаемое объектами памяти, на которые нет ссылок, автоматически восстанавливается сборщиком мусора во время выполнения. [20]

Указатели реализованы во многом так же, как в Паскале, как и VAR параметры при вызове процедур. Modula-2 даже более строго типизирован, чем Pascal, и имеет меньше способов выйти из системы типов. Некоторые варианты Modula-2 (например, Modula-3 ) включают сбор мусора.

Как и в случае с Модулой-2, здесь доступны указатели. Способов обойти систему типов все еще меньше, поэтому Оберон и его варианты по-прежнему более безопасны в отношении указателей, чем Модула-2 или его варианты. Как и в случае с Modula-3 , сбор мусора является частью спецификации языка.

В отличие от многих языков, в которых есть указатели, стандарт ISO Pascal позволяет указателям ссылаться только на динамически созданные переменные, которые являются анонимными, и не позволяет им ссылаться на стандартные статические или локальные переменные. [21] Он не имеет арифметики указателей. У указателей также должен быть связанный тип, и указатель на один тип несовместим с указателем на другой тип (например, указатель на символ несовместим с указателем на целое число). Это помогает устранить проблемы безопасности типов, присущие другим реализациям указателей, особенно тем, которые используются для /I или C. PL Это также устраняет некоторые риски, вызванные висячими указателями , но дает возможность динамически освобождать ссылочное пространство с помощью dispose стандартная процедура (которая имеет тот же эффект, что и free библиотечная функция, найденная в C ), означает, что риск зависания указателей не устранен полностью. [22]

Однако в некоторых коммерческих реализациях компиляторов Pascal (или его производных) с открытым исходным кодом, таких как Free Pascal , [23] Turbo Pascal или Object Pascal в Embarcadero Delphi — указатель может ссылаться на стандартные статические или локальные переменные и может быть преобразован из одного типа указателя в другой. Более того, арифметика указателей не ограничена: добавление или вычитание указателя перемещает его на указанное количество байтов в любом направлении, но с использованием Inc или Dec стандартные процедуры с его помощью перемещают указатель на размер типа данных, он объявлен на который . Нетипизированный указатель также предоставляется под именем Pointer, который совместим с другими типами указателей.

Язык Perl программирования поддерживает указатели, хотя и редко используемые, в виде функций упаковки и распаковки. Они предназначены только для простого взаимодействия со скомпилированными библиотеками ОС. Во всех остальных случаях Perl использует типизированные ссылки , которые не допускают какой-либо формы арифметики указателей. Они используются для создания сложных структур данных. [24]

См. также

[ редактировать ]

Примечания

[ редактировать ]
  1. ^ Некоторые компиляторы позволяют хранить адреса функций в указателях void. В стандарте C++ указано преобразование указателя функции в void* как условно поддерживаемая функция, а стандарт C гласит, что такие преобразования являются «общими расширениями». Это требуется для POSIX. функции dlsym. [15]
  1. ^ Дональд Кнут (1974). «Структурное программирование с переходом к операторам» (PDF) . Вычислительные опросы . 6 (5): 261–301. CiteSeerX   10.1.1.103.6084 . дои : 10.1145/356635.356640 . S2CID   207630080 . Архивировано из оригинала (PDF) 24 августа 2009 г.
  2. ^ Рейли, Эдвин Д. (2003). Вехи в области компьютерных наук и информационных технологий . Издательская группа Гринвуд. п. 204 . ISBN  9781573565219 . Проверено 13 апреля 2018 г. Указатель Гарольда Лоусона.
  3. ^ «Список наград Компьютерного общества IEEE» . Награды.computer.org. Архивировано из оригинала 22 марта 2011 г. Проверено 13 апреля 2018 г.
  4. ^ ISO/IEC 9899 , ​​пункт 6.7.5.1, параграф 1.
  5. ^ ISO/IEC 9899 , ​​пункт 6.7.8, параграф 10.
  6. ^ ISO/IEC 9899 , ​​пункт 7.17, параграф 3: NULL... который расширяется до определяемой реализацией константы нулевого указателя...
  7. ^ ISO/IEC 9899 , ​​пункт 6.5.3.2, параграф 4, сноска 87: Если указателю присвоено недопустимое значение, поведение унарного оператора * не определено... Среди недопустимых значений для разыменования указателя с помощью унарный оператор * является нулевым указателем...
  8. ^ Jump up to: а б Плаугер, Пи Джей ; Броди, Джим (1992). Справочник программиста C по стандартам ANSI и ISO . Редмонд, Вашингтон: Microsoft Press. стр. 108, 51 . ISBN  978-1-55615-359-4 . Тип массива не содержит дополнительных дыр, поскольку все остальные типы плотно упаковываются при составлении в массивы [на странице 51].
  9. ^ WG14 N1124 , C – Утвержденные стандарты: ISO/IEC 9899 – Языки программирования – C , 06 мая 2005 г.
  10. ^ Юнг, Ральф. «Указатели сложны II, или: Нам нужны лучшие языковые спецификации» .
  11. ^ Юнг, Ральф. «Указатели сложны, или: что содержится в байте?» .
  12. ^ Патент США 6625718 , Штайнер, Роберт К. (Брумфилд, Колорадо), «Указатели, которые относятся к их собственному текущему местоположению», выдан 23 сентября 2003 г., передан Avaya Technology Corp. (Баскин-Ридж, Нью-Джерси).  
  13. ^ патент США 6115721 , Надь, Майкл (Тампа, Флорида), «Система и метод сохранения и восстановления базы данных с использованием самоуказателей», выдан 5 сентября 2000 г., передан IBM (Армонк, Нью-Йорк).  
  14. ^ «Базовые указатели» . Msdn.microsoft.com . Проверено 13 апреля 2018 г.
  15. ^ «Выпуск 195 CWG» . cplusplus.github.io . Проверено 15 февраля 2024 г.
  16. ^ «Указатели на функции-члены» . isocpp.org . Проверено 26 ноября 2022 г.
  17. ^ «c++filt(1) — справочная страница Linux» .
  18. ^ «Итаниум C++ ABI» .
  19. ^ Ульф Билтинг, Ян Скансхольм, «Vägen до C» (Дорога к C), третье издание, стр. 169, ISBN   91-44-01468-6
  20. ^ Ник Парланте, [1] , Стэнфордская образовательная библиотека по информатике , стр. 9–10 (2000).
  21. ^ Стандарт ISO 7185 Pascal (неофициальная копия), раздел 6.4.4 Типы указателей. Архивировано 24 апреля 2017 г. на Wayback Machine и позднее.
  22. ^ Дж. Уэлш, У. Дж. Снерингер и К. А. Хоар, «Двусмысленность и неуверенность в Паскале», Программное обеспечение: Практика и опыт 7 , стр. 685–696 (1977).
  23. ^ Справочник по бесплатному языку Pascal, раздел 3.4 Указатели
  24. ^ Контактные данные. "// Создание ссылок (ссылки Perl и вложенные структуры данных)" . Perldoc.perl.org . Проверено 13 апреля 2018 г.
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 7433d977ecb571836b63637061e70812__1717445940
URL1:https://arc.ask3.ru/arc/aa/74/12/7433d977ecb571836b63637061e70812.html
Заголовок, (Title) документа по адресу, URL1:
Pointer (computer programming) - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)