Дональд Кнут, Структурированное программирование, с переходом к использованию
Указатель a, указывающий на адрес памяти, связанный с типом б . На этой диаграмме вычислительная архитектура использует одно и то же адресное пространство и примитив данных как для указателей, так и для не указателей; в этом не должно быть необходимости.В информатике указатель объект во многих языках программирования, который хранит адрес памяти . Это может быть другое значение, находящееся в памяти компьютера, или, в некоторых случаях, значение с отображением памяти компьютерное оборудование. Указатель указан на место в памяти, и получение значений, хранящегося в этом месте, известно как разыменование указателя. По аналогии, номер страницы в указателе книги можно рассматривать как указатель на соответствующую страницу; разыменование такого указателя будет осуществляться путем перехода на страницу с заданным номером и чтения текста, найденного на этой странице. Фактический формат и стандартные данные зависят от модели компьютерной архитектуры.
Использование указателей улучшает производительность для повторяющихся операций, таких как переход итеративных структуры (например, строки, таблицы поиска, управляющие таблицы и древовидные структуры). В частности, копирование и разыменование указателей часто обходится гораздо дешевле по времени и пространству.
Указатели также используются для хранения адресов точек входа для вызываемых подпрограмм в процедураном программировании и для связывания во время выполнения с библиотеками динамической компоновки (DLL). В объектно-ориентированном программировании, указатели на функции используются для привязки методов, часто с использованием таблиц виртуальных методов.
Указатель - это простая, более конкретная реализация более абстрактного типа данных reference . Некоторые языки, особенно языки низкого уровня, включают некоторые типы указателей, хотя некоторые из них имеют больше ограничений на их использование, чем другие. Хотя «указатель» используется для ссылок на ссылки в целом, он более правильно применяется к структуре данных, чей интерфейс явно позволяет манипулировать указателем (арифметически с помощью арифметики) как адрес памяти, в отличие от magic cookie или возможностей, которые не позволяют это. Указатели позволяют использовать как защищенный, так и незащищенный доступ к адресам памяти, с их использованием связаны риски, особенно в последнем случае. Примитивные указатели часто хранятся в формате, аналогичном целочисленному ; однако попытка разыменования или "поиска" такого указателя, значение которого не является допустимым адресом памяти, приведет к сбою программы . Чтобы использовать эту возможность типа потенциальную проблему, в обеспечении безопасности указатели типа указаны в параметрах, указанных типом данных, даже если базовое представление является целым числом. Также могут быть предприняты другие меры (например, проверка и проверка границ ), чтобы убедиться, что переменная-указатель содержит значение, одновременно является допустимым адресом памяти и находится в числовом диапазоне, процессор способности к адресации.
В 1955 г. Советский ученый-компьютерщик Кат Ющенко изобрела Адресный язык программирования, который сделал возможным косвенную адресацию адреса высшего ранга, аналогичные указатели. Этот язык широко использовался на компьютерах Советского Союза. Однако за пределами Советского Союза он был неизвестен, и обычно Гарольду Лоусону приписывают указателя в 1964 году. В 2000 году Лоусон был удостоен награды Computer Pioneer Award посредством IEEE "[f] или за возможность использования-указателя и внедрения концепции в PL / I, тем самым впервые предоставив возможность гибко обрабатывать связанные списки на универсальном языке высокого уровня уровня ». Его основополагающий документ о концепциях появился в июньском выпуске CACM за 1967 год, озаглавленном «Обработка списков PL / I». Согласно Oxford English Dictionary, указатель слово появилось впервые в печати как указатель стека в техническом меморандуме System Development Corporation.
В информатике указатель - это своего рода ссылка.
Примитив данных (или просто примитив) - это любые данные, которые могут быть прочитаны или записаны в память компьютера. использование одного доступа к памяти (например, и байт, и слово являются примитивами).
Агрегат данных (или просто агрегат) - это группа примитивов, которые логически непрерывны в памяти и рассматриваются вместе как один элемент данных (например, агрегат может быть 3 логически связными байтов, значения которых представить 3 координаты точки в пространстве). Когда агрегат полностью состоит из примитива того же типа, агрегат можно назвать массивом ; в некотором смысле примитив многобайтового слова представляет собой массив байтов, и некоторые программы используют таким образом.
В контексте этих определений байт - это наименьший примитив; каждый адрес памяти указывает отдельный байт. Адрес памяти начального байта данных считается адресом памяти (или базовым адресом памяти) всей базы данных.
Указатель памяти (или просто указатель) - это примитив, значение которого предназначено для использования в качестве адреса памяти; говорят, что указатель указывает на адрес памяти. Также говорится, что указатель указывает на элемент данных [в памяти], когда значение указателя является адресом в памяти элемента данных.
в более общем смысле, указатель является указателем своего рода ссылка, и говорят, что указатель приведен на данные, хранящиеся где-то в памяти; чтобы получить эту базу данных, нужно разыменовать указатель. Особенность, которая отделяет указатели от других видов ссылок, заключается в том, что значение указателя должно интерпретироваться как адрес памяти, что является довольно низкоуровневой концепцией.
Место установки места косвенности: значение указателя определить, какой адрес памяти следует использовать в вычислении. Альтернативное обращение - фундаментальный аспект алгоритмов, указатели часто выражаются как фундаментальный тип данных на языках программирования ; в статически (или строго ) языках программирования, тип указывает указатель определяет тип данных, на которые указывает.
При настройке структур данных, таких как списки, очереди и деревья, необходимо иметь указатели, помогающие управлять реализацией и контролем структур. Типичными примерами указателей являются указатели начала, указатели конца и указатели стека и. Эти указатели могут быть абсолютными (фактический физический адрес или виртуальный адрес в существ памяти ) или относительными (смещение от абсолютного начального адреса («базы»), которое обычно использует меньше битов, чем полный адрес, но обычно требует одной дополнительной арифметической операции для разрешения).
Относительные адреса - это форма ручной сегментации памяти, которая имеет ее преимущество и недостатков. Двухбайтовое смещение, содержащее 16-битное целое число без знака, может установить для относительной адресации до 64 KiB (2 байта) структуры данных. Его можно легко расширить до 128, 256 или 512, если адрес, на который принудительно должен быть выровнен на границе полуслова, слова или двойного слова (но при этом требуется дополнительный «сдвиг влево» поразрядная операция - 1, 2 или 3 бита - для корректировки ущерба с коэффициентом 2, 4 или 8 перед его добавлением к базовому адресу). Однако, как правило, такие схемы доставляют много проблем, и для удобства программиста предпочтительны предпочтительные адреса (и лежащие в основе этого плоское адресное пространство ).
Однобайтное смещение, такое как шестнадцатеричное значение ASCII символов (, X'29 '), может быть указание на альтернативное целочисленное значение (или индекс) в массиве. (например, X'01 '). Таким образом, символы могут быть очень эффективно преобразованы из «необработанных данных » в пригодный для использования последовательный индекс, а в абсолютный адрес без таблицы поиска .
Управляющие таблицы, которые используются для управления программным потоком, обычно широко используют указатели. Указатели, обычно введенные в запись таблицы, могут быть введены, например, установка для точек входа в подпрограммы. Использование различных фактических адресов или адресов (в зависимости от доступных конструкций языка программирования). Их также можно использовать для указания на более ранние записи таблицы (как при обработке) или для пересылки, чтобы пропустить некоторые записи таблицы (как в переключателе или «раннем» цикла выхода из цикла). Для этой последней цели «указатель» может быть просто самим номером записи в таблице и может быть преобразован в фактический адрес простой арифметикой.
Указатели - это очень тонкая абстракция, дополнительная возможность адресации, предоставляющая большинство современных архитектур. В простейшей схеме каждому блоку памяти в системе назначается адрес или числовой индекс, где обычно используется либо байт. или слово - в зависимости от того, является ли архитектура с байтовой адресацией или с адресом по слову - эффективно преобразовывая всю память в очень большой массив. Затем система также предоставит операцию по извлечению значения, хранящегося в блоке памяти по заданному адресу (обычно с использованием машинных регистров общего назначения ).
В обычном размере указателя достаточно велик, чтобы содержать больше адресов, чем установлено хранилище в системе. Эта программа может попытаться получить доступ к адресу, не соответствует ни одной единице памяти, либо потому, что установлено недостаточное количество памяти (то есть установлено вне диапазона доступной памяти), либо архитектура не поддерживает такие адреса. Первый случай на некоторых платформах, таких как архитектура Intel x86, может называться segmentation fault (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-списках, эти инструменты упрощают рекурсивные средства для создания и обработки данных - например, путем рекурсивного доступа к элементам заголовка и хвоста списков списков; например "забирая машину из компакт-диска". Напротив, управление памятью на основе разыменования указателя в некотором приближении к массиву адресов памяти облегчает обработку как слотов, которые могут быть назначены данные императивно.
При работе с массивами критическое значение операция поиска обычно включает этап, называемый вычислением адреса, который включает построение указателя на желаемый элемент данных в массиве. В других структурах данных, таких как связанные списки, используются указатели как ссылки для явной привязки одной части структуры к другой.
Указатели используются для передачи параметров по ссылке. Это полезно, если программист хочет, чтобы изменения в параметрах были видны вызывающую функцию. Это также полезно для возврата нескольких значений из функции.
Указатели также Программу для выделение и освобождение динамических чисел и массивов в памяти. Когда она больше не нужна, используется пустое пространство. Несоблюдение этого правила может привести к избыточной памяти (когда доступная свободная память постоянно или в тяжелых случаях быстро из-за накопления избыточных блоков памяти).
Базовый синтаксис для определения указателя:
int * ptr;
Это объявляет ptr
как идентификатор объекта следующего типа:
int
Обычно это выражается более кратко как "ptr
- указатель на int
. "
Язык C не определяет неявную инициализацию для объектов с автоматической продолжительностью хранения, следует часто проявлять осторожность, чтобы адрес, который указывает на ptr
, действителен; вот почему иногда предлагается явно инициализировать указатель значением нулевого указателя, которое традиционно указывается в C с помощью стандартизованного макроса NULL
:
int * ptr = NULL;
Разыменование нулевого указателя в C приводит к неопределенному поведению, которое может быть катастрофическим. Однако большинство реализаций просто останавливают выполнение рассматриваемой программы, обычно из-за ошибки сегментации .
. Однако необоснованная инициализация указателей может затруднить анализ программы, тем самым скрывая ошибки.
В любом случае после объявления указателя следующим логическим шагом будет указание на что-то:
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 |
(Показанный здесь нулевой указатель равен 0x00000000.) Назначив адрес a
на ptr
:
ptr = a;
возвращает следующие значения памяти:
Address | Contents |
---|---|
0x8130 | 0x00000005 |
0x8134 | 0x00008130 |
Затем путем разыменования ptr
с помощью кодировка:
* ptr = 8;
компьютер примет содержимое ptr
(который равен 0x8130), «найдет» этот адрес и назначит 8 этому месту, в результате чего получится следующая память:
Address | Содержание |
---|---|
0x8130 | 0x00000008 |
0x8134 | 0x00008130 |
Очевидно, что при доступе к a
будет получено значение 8, поскольку предыдущая инструкция изменила содержимое a
посредством указателя ptr
.
В C индексирование массивов формально определяется в терминах арифметики указателей; то есть спецификация языка требует, чтобы array [i]
был эквивалентен * (array + i)
. Таким образом, в C массивы можно рассматривать как указатели на последовательные области памяти (без пробелов), а синтаксис для доступа к массивам идентичен синтаксису, который может использоваться для разыменования указателей. Например, массив array
можно объявить и использовать следующим образом:
int array [5]; / * Объявляет 5 смежных целых чисел * / int * ptr = array; / * Массивы могут использоваться как указатели * / ptr [0] = 1; / * Указатели можно индексировать с помощью синтаксиса массива * / * (array + 1) = 2; / * Массивы можноразыменовать с помощью синтаксиса указателя * / * (1 + массив) = 2; / * Добавление указателя коммутативно * / array [2] = 4; / * Оператор нижнего индекса является коммутативным * /
Он выделяет блок из пяти целых чисел и называет блок массивом
, который действует как указатель на блок. Другое распространенное использование указателей - указание на динамически выделяемую память из malloc, возвращает последовательный блок памяти не меньше запрошенного размера, который можно использовать как массив.
Хотя большинство операторов для массивов и указателей эквивалентны, результат оператора размер
отличается. В этом примере sizeof (array)
будет оцениваться как 5 * sizeof (int)
(размер массива), а sizeof (ptr)
будет оценивать до sizeof (int *)
, размер самого указателя.
Значения по умолчанию могут быть объявлены следующим образом:
int array [5] = {2, 4, 3, 1, 5};
Если массив
расположен в памяти, начиная с адреса 0x1000 на 32-битной машине little-endian, тогда память будет содержать следующее (значения в шестнадцатеричном формате, как и адреса):
0 | 1 | 2 | 3 | |
---|---|---|---|---|
1000 | 2 | 0 | 0 | 0 |
1004 | 4 | 0 | 0 | 0 |
1008 | 3 | 0 | 0 | 0 |
100C | 1 | 0 | 0 | 0 |
1010 | 5 | 0 | 0 | 0 |
Здесь представлены пять целых чисел: 2, 4, 3, 1 и 5. Эти пять целых чисел занимают 32 бита (4 байта), каждый из которых младший байт хранится первым (это архитектура ЦП с прямым порядком байтов ) и сохраняются следующие, начиная с адреса 0x1000.
Синтаксис языка C с указателями:
массив
означает 0x1000;массив + 1
означает 0x1004: «+ 1» означает прибавление размера 1 int
, что составляет 4 байта;* массив
означает разыменование содержимого массив
. Рассматривая содержимое как адрес памяти (0x1000), найдите значение в этом месте (0x0002);array [i]
означает номер элемента i
, отсчитывается от 0, из array
, который переводится в * (array + i)
.Последний пример - как получить доступ к содержимому массив
. Разбивка на части:
array + i
- это место в памяти элемента (i) из array
, начиная с i = 0;* (array + i)
берет этот адрес памяти и разыменовывает его для доступа к значению.Ниже приведен пример определения указанного списка в C.
/ * пустой связанный список представлен NULL * или другим контрольным числом * / #define EMPTY_LIST NULL struct ссылка {void * data; / * данные этой ссылки * / struct link * next; / * следующая ссылка; EMPTY_LIST, если его нет * /};
Это определение рекурсивного указателя по существу же, как определение рекурсивного указателя из языка программирования Haskell :
data Link a = Nil | Минусы a (Link a)
Nil
- пустой список, а Cons a (Link a)
- это cons ячейка типа a
с другой сообщение типа a
.
Определение со ссылками, однако, проверяется на тип и использует вводящие в заблуждение значения сигналов. По этой структуре данных в C обычно обрабатываются функции-оболочки, которые проверяются на правильность.
Указатели могут передать значение по их адресу, что позволяет использовать их значение. Например, рассмотрим следующий код C :
/ * copy int n можно изменить внутри функции, не исследуя вызывающий код * / void passByValue (int n) {n = 12; } / * вместо этого передается указатель m. Копия значения, которое указывает m, не создается * / void passByAddress (int * m) {* m = 14; } int main (void) {int x = 3; / * передать копию значения x в качестве аргумента * / passByValue (x); // значение было изменено внутри функции, но с этого момента x по-равенству 3 / * передать адрес x в качестве аргумента * / passByAddress (x); // функция x был фактически изменена и теперь равенство 14 здесь return 0; }
В некоторых программах необходимая память зависит от того, что может иметь пользователь. В таких случаях программисту необходимо динамически распределять память. Это делается путем выделения памяти в куче, а не в стеке, где обычно хранятся переменные (переменные также могут храниться в регистрах ЦП, но это другое дело). Распределение динамической памяти может быть выполнено только с помощью указателей, а имена (например, с общими переменными) не могут быть указаны.
Указатели используются для хранения и управления адресами динамически выделяемых блоков памяти. Такие блоки используются для хранения объектов данных или массивов объектов. Большинство структурированных и объектно-ориентированных языков используют область памяти, выделяемую кучей или свободным хранилищем, из которой динамически создаются объекты.
В примере кода C ниже показано, как объекты структуры динамически выделяются и на них появляются. Стандартная библиотека C предоставляет функцию malloc()
для выделения блоков памяти из кучи. Он принимает размер объекта для выделения в качестве и возвращает блок указатель на вновь выделенный, подходящий для хранения объекта, или возвращает нулевой указатель, если выделение не удалось.
/ * Элемент инвентаризации запчастей * / struct Item {int id; / * Номер детали * / char * имя; / * Название детали * / float cost; / * Стоимость * /}; / * Выделить и инициализировать новый объект Item * / struct Item * make_item (const char * name) {struct Item * item; / * Выделяем блок памяти для нового объекта Item * / item = malloc (sizeof (struct Item)); если (item == NULL) вернуть NULL; / * Инициализируем элементы нового элемента * / memset (item, 0, sizeof (struct Item)); элемент->id = -1; item->name = NULL; элемент->стоимость = 0,0; / * Сохраняем копию имени в новом элементе * / item->name = malloc (strlen (name) + 1); если (элемент->имя == NULL) {бесплатно (элемент); return NULL; } strcpy (элемент->имя, имя); / * Возвращаем вновь созданный объект Item * / return item; }
В приведенном ниже коде показано, как объекты памяти динамически освобождаются, т. Е. Возвращаются в кучу или в свободное хранилище. Стандартная библиотека C функцию функцию free()
для освобождения ранее выделенного блока памяти и возврат его обратно в кучу.
/ * Освободить объект Item * / void destroy_item (struct Item * item) {/ * Проверить указатель на нулевой объект * / if (item == NULL) return; / * Освободить строку имени, сохраненную в элементенте * / if (item->name! = NULL) {free (item->name); item->name = NULL; } / * Освобождаем сам объект Item * / free (item); }
На некоторых вычислительных архитектурах указатели приложения для непосредственного управления памятью или устройством с отображением в память.
Назначение указателям - бесценный инструмент при программировании микроконтроллеров. Ниже приведен простой пример объявления указателя типа int и инициализации его шестнадцатеричным адресом в этом примере константой 0x7FFF:
int * hardware_address = (int *) 0x7FFF;
В середине 80-х использование BIOS для доступа к видео-возможностям ПК было медленным. Приложения, которые требовали интенсивного отображения, обычно использовались для преобразования шестнадцатеричной константы CGA путем преобразования шестнадцатеричной константы 0xB8000 в указатель на массив из 80 беззнаковых 16-битных значений типа int. Каждое значение состояло из кода ASCII в младшем байте и цвета в старшем байте. Таким образом, чтобы разместить букву 'A' в строке 5, столбце 2 ярко-белым цветом на синем, можно было бы написать следующий код:
#define VID ((unsigned short (*) [80]) 0xB8000) void foo (void) {VID [4] [1] = 0x1F00 | 'А'; }
. Во многих языках указывается дополнительное ограничение, заключающееся в том, что объект, на который они указывают, имеет определенный тип. Например, может быть объявлен указатель, указывающий на целое ; затем язык будет пытаться помешать программисту указывать ему на объекты, которые не являются целыми числами, такими как числа с плавающей запятой, устраняя некоторые ошибки.
Например, в C
int * деньги; чар * мешки;
деньги
будет целочисленным указателем, а сумка
будет указателем указателем. Следующее к предупреждению компилятора о назначении из несовместимого типа указателя в GCC
bag = money;
потому что деньги
и мешки
были заявлены с разными типами. Чтобы подавить предупреждение компилятора, необходимо явно указать, что вы действительно хотите выполнить присвоение с помощью приведения типов it
bag = (char *) money;
, который говорит о приведении целочисленного указателя деньги
к указателю и назначению сумка
.
Черновик стандарта C 2005 года требует, чтобы указатель, производный от одного типа, к другому типу должен поддерживать правильность выравнивания для обоих типов (6.3.2.3 Указатели, пар. 7):
char * external_buffer = "abcdef"; int * internal_data; внутренние_данные = (число *) внешний_буфер; // НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ, если «результирующий указатель // неправильно выровнен»
В языках, допускающих арифметические операции с указателями, арифметические операции с указателями принимают во внимание размер типа. Например, добавление целого числа к указателю дает другой указатель, который указывает на адрес, который на это число больше размера типа. Это позволяет нам легко вычислять элементы массива заданного типа, как показано в приведенном выше примере массива C. Когда указатель одного типа к другому типу другого размера, программист должен ожидать, что арифметика указателя будет вычисляться по-другому. В C, например, если массив money
начинается с 0x2000 и sizeof (int)
составляет 4 байта, тогда как sizeof (char)
равен 1 байту, то деньги + 1
будут указывать на 0x2004, но сумки + '1'
будут указывать на 0x2001. Другие приведения типов включают в себя потерю данных, когда «широкие» данные записываются в «узкие» места (например, bag [0] = 65537;
), неожиданные результаты при сдвиге битов значений и проблемы сравнения, особенно со значениями со знаком и без знака.
Хотя в целом невозможно определить во время компиляции, какие преобразования являются безопасными, некоторые языки хранят информацию о типе времени выполнения, которая может подтвердить то, что эти опасные преобразования допустимы в время выполнения. Другие языки просто принимают, консервативное приближение безопасных рекомендаций или вообще не принимают.
Временный указатель позволяет попытаться получить доступ к объекту, который может быть не определен, указатели могут быть установки ошибок программирования. Однако полезность указателей настолько велика, что без них может быть сложно выполнять задачи программирования. Таким образом, созданы различные конструкции, предназначенные для некоторых полезных функций указателей некоторых из их ловушек, также иногда называемых опасностями. В этом контексте указатели, которые используются напрямую в этой статье, называются необработанными указателями, в отличие от интеллектуальных указателей или других вариантов.
Одна из основных проблем с указателями заключается в том, что до тех пор, пока ими можно напрямую манипулировать как числа, они указывают на используемые адреса или данные, которые используются для других целей. Многие языки, включая большинство языков, функционального программирования и недавние императивные языки, такие как Java, заменяют указатели на более непрозрачный тип ссылки, называемый просто язык, который может только для ссылки на объекты и не может этот номер как число, что предотвращает этот тип ошибок. Индексирование обзора как особый случай.
Указатель, которому не назначен какой-либо адрес, называется диким указателем. Любая попытка использовать такие неинициализированные указатели может вызвать неожиданное поведение либо потому, что начальное значение не является допустимым адресом, либо потому, что его использование может повредить другие части программы. Результатом часто бывает ошибка сегментации, нарушение памяти или дикая ветвь (если используется как указатель на функцию или адрес ветвления).
В системах с явным выделением памяти можно создать висячий указатель, освободив область памяти, на которую он указывает. Этот тип указателя опасен и незаметен, потому что освобожденная область памяти может содержать те же данные, что и до ее освобождения, но затем может быть перераспределена и перезаписана несвязанным кодом, неизвестным предыдущему коду. Языки с сборкой мусора предотвращают этот тип ошибки, потому что освобождение выполняется автоматически, когда в области больше нет ссылок.
Некоторые языки, например C ++, поддерживают интеллектуальные указатели, которые используют простую форму подсчета ссылок для отслеживания распределения динамической памяти в в дополнение к действию в качестве ссылки. В отсутствие циклов ссылок, когда объект косвенно ссылается на себя через последовательность интеллектуальных указателей, они исключают возможность «висящих» указателей и утечек памяти. Строки Delphi изначально поддерживают подсчет ссылок.
Язык программирования Rust представляет средство проверки заимствований, время жизни указателя и оптимизацию, основанную на необязательных типах для нулевых указателей для устранения ошибок указателя, без обращения к сборке мусора.
A нулевой указатель имеет значение, зарезервированное для указания того, что указатель не ссылается на действительный объект. Нулевые точки обычно используются для представления таких условий, как конец какого списка неизвестной длины или отказа выполнить-либо действие; такое использование нулевых указателей можно сравнить с типами, допускающими значение NULL, и со значением NULL в типе параметра .
Автоотносительный указатель равен указатель, значение которого интерпретируется как смещение от адреса самого указателя; таким образом, если структура данных имеет элемент автоотносительного указателя, который указывает на некоторую часть самой структуры данных, то структура данных может быть перемещена в память без необходимости обновления значений автоматического относительного указателя.
В цитируемом патенте также используется термин относительный для обозначения того же самого. Однако значение этого термина использовалось и по-другому:
A Указатель на основе - это указатель, значение которого является смещением от значения другой указатель. Это можно использовать для хранения блоков данных, присвоения адреса начала блока базовому указателю.
На некоторых языках указатель может ссылаться на другой указатель, требуя нескольких операций разыменования для получения исходного значения. Хотя каждый уровень косвенного обращения может повысить производительность, иногда это необходимо для правильного поведения сложного структурных данных. Например, в C обычно определяют связанный список в терминах элемента, который содержит указатель на следующий элемент списка:
struct element {struct element * next; значение int; }; элемент структуры * head = NULL;
Эта реализация использует указатель на первый элемент в списке в качестве суррогата для всего списка. Если в начало списка добавлено новое значение, необходимо изменить head
, чтобы он указывал на новый элемент. Использование двойного косвенного средства позволяет правильно реализовать вставку и имеет желаемый побочный эффект в виде исключения кода случая для обработки вставок в начале списка:
// Учитывая отсортированный список в * h ead, вставьте элемент элемента в первое // место, где все предыдущие элементы меньшее или равное значение. void insert (struct element ** head, struct element * item) {struct element ** p; // p указывает на указатель на элемент for (p = head; * p! = NULL; p = (* p) ->next) {if (item->value <= (*p)->value) break; } item->next = * p; * p = элемент; } // Вызывающий делает это: insert (head, item);
В этом случае, если значение item
меньше, чем значение head
, вызывающая head
правильно обновляет адрес нового элемента.
Базовый пример - аргумент argv для основной функции в C (и C ++), который в прототипе представлен как char ** argv
- это потому, что переменная argv
сама по себе является указателем на массив строк (массив массивов), поэтому * argv
является указателем на 0-ю строку (по согласованию программы имя), а ** argv
- это 0-й символ 0-й строки.
На некоторых языках указатель может ссылаться на исполняемый код, т.е. он может указывать на функцию, метод или функцию. Указатель на функцию будет хранить адрес вызываемой функции. Хотя это средство можно использовать для динамического вызова функций, оно часто является излюбленным приемом авторов вирусов и других программ.
int sum (int n1, int n2) {// с двумя целочисленными функциями, возвращающими целочисленное значение return n1 + n2; } int main (void) {int a, b, x, y; int (* fp) (интервал, интервал); // Указатель функции, который может указывать на такую функцию, как sum fp = ∑ // fp теперь указывает на функцию sum x = (* fp) (a, b); // Вызывает функцию sum с аргументами a и b y = sum (a, b); // Вызывает функцию суммы с аргументами a и b}
A висячий указатель - это указатель, который не указывает на действительный объект и, следовательно, может привести к сбою программе или странному поведению. На языках программирования Pascal или C указатели, которые специально не инициализированы, могут указывать на непредсказуемые адреса в памяти.
В следующем примере показан висящий указатель:
int func (void) {char * p1 = malloc (sizeof (char)); / * (undefined) значение некоторого места в куче * / char * p2; / * висячий (неинициализированный) указатель * / * p1 = 'a'; / * Это нормально, если malloc () не вернул NULL. * / * p2 = 'b'; / * Это вызывает неопределенное поведение * /}
Здесь p2
может указывать на любое место в памяти, поэтому выполнение присваивания * p2 = 'b';
может повредить неизвестную область памяти или вызвать ошибку сегментации .
В два раза связанных списках или древовидных структур обратный указатель, удерживаемый на элементах назад 'к элементу, относящемуся к текущему элементу. Они полезны для навигации и манипуляций за счет большего использования памяти.
Где используется как адрес точки входа в программу или в качестве начала функций , что ничего не возвращает, а также либо неинициализированный или поврежденный, если вызов или переход все же сделан по этому адресу, считается, что произошла «дикая ветвь ». Последствия обычно непредсказуемы, и ошибка может проявляться по-разному, в зависимости от того, является ли указатель «допустимым» адресом и есть ли (по совпадению) действующая инструкция (код операции) по этому адресу. Одно из самых сложных инструкций может представлять собой одно из самых сложных инструкций по диагностике, поскольку большая часть испытаний может быть уже уничтожена заранее или в результате выполнения одного или несоответствующих инструкций в соответствии с инструкциями. Если доступно, имитатор набора инструкций обычно может не только обнаруживать дикая ветвь до того, как она вступает в силу, но также предоставляет полную или частичную трассировку ее.
Можно моделировать поведение указателя, используя индекс для (обычно одномерного) массива.
В первую очередь для языков, которые явно не указатели, но массивы, массив можно рассматривать и обрабатывать так, как если бы он был всем диапазоном (в рамках конкретного массива) и любой индекс к нему можно рассматривать как эквивалент регистрационного назначения на языке ассемблера (который указывает на байты, но фактическое значение которого относится к началу массива, а не к его абсолютному адресу в памяти). Предполагаемая, что массив представляет собой, скажем, непрерывную 16 мегабайтную символьную преобразовать данные, можно напрямую адресовать отдельные байты (или строку из других байтов в массиве). и управляется с использованием имени массива с 31-битным целым числом без знака в качестве имитируемого массива указателя (это очень похоже на пример массивов C, показанный выше). Арифметику указателя можно смоделировать путем добавления или вычитания из индекса с минимальными дополнительными издержками по сравнению с настоящим арифметикой указателя.
Это даже теоретически возможно, используя вышеупомянутую технику вместе с подходящим имитатором команд набора для имитации любого машинного кода или промежуточного (байтового кода ) процессора / языка на другом языке, который вообще не поддерживает указатели (например, Java / JavaScript ). Для этого двоичный код может быть изначально загружен в наборные байты массив, чтобы симулятор «считывал», интерпретировал и выполнял действия полностью в память, содержит в том же самом массиве. Если необходимо, чтобы полностью избежать проблем переполнения буфера, проверка границ может быть фермента для компилятора (или, если нет, обычно кодируется вручную в симуляторе).
Ada - это строго типизированный язык, в котором все указатели являются типизированными и разрешенными только безопасными преобразованиями типов. Все указатели по умолчанию инициализируются значением null
, и любая попытка доступа к данным через указатель null
вызывает исключение . Указатели в Ada называются типами доступа. В Ada 83 не разрешена арифметика для типов доступа (хотя многие поставщики наборов этих в качестве нестандартных функций), но Ada 95 поддерживает «безопасную» арифметику для типов доступа через пакет System.Storage_Elements
.
В нескольких старых версиях BASIC для платформы Windows была поддержка STRPTR () для возврата строки и для VARPTR () для возврата альтернативного адреса. Visual Basic 5 также поддерживает OBJPTR () для возврата адреса объектного интерфейса и для оператора ADDRESSOF для возврата адреса функции. Все они представляют собой целые числа, но их значения эквивалентны значениям, хранящимся в типах указателей.
Более новые диалекты BASIC, такие как FreeBASIC или BlitzMax, однако, имеют исчерпывающие реализации указателей. В FreeBASIC арифметические операции с указателями ЛЮБОЙ
(эквивалентными void *
языка C) обрабатываются так, как если бы указатель ЛЮБОЙ
был байтовой шириной. ЛЮБЫЕ указатели
не могут быть разыменованы, как в C. Кроме того, приведение типов между ЛЮБОЙ
и указателями любого другого типа не будет генерировать никаких предупреждений.
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 ++ указатели - это переменные, которые хранят адреса и могут быть нулевыми. Каждый указатель имеет тип, на который он указывает, но можно произвольно преобразовывать типы указателей (но не между указателем и указателем объекта). Специальный тип указателя, называемый «пустым указателем», позволяет указывать на любой (нефункциональный) объект, но ограничен тем, что его разыменовать напрямую (он должен быть приведен). Самым адресом часто можно напрямую манипулировать, приводя указатель к целочисленному типу достаточного размера и от него, хотя определяется реализация и действительно может вызвать неопределенное поведение; в то время как более ранние стандарты C не имели интегрального типа, который гарантированно был достаточно большим, C99 указывает имя uintptr_t
typedef, определенное в
, но реализация не требует этого.
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)
, который является содержимым элемента, на котором указывает а + п
. Это означает, что n [a]
эквивалентно a [n]
, и можно записать, например, a [3]
или 3 [a]
одинаково хорошо для доступа к четвертому элементу массива a
.
Несмотря на свою мощь, арифметика с указателями может быть источником компьютерных ошибок. Оно имеет тенденцию сбивать с толку новичков программистов, заставляя их использовать разные контексты: выражение может быть обычным арифметическим или арифметическим указателем, и иногда легко принять одно за другое. В ответ на это многие современные компьютерные языки высокого уровня (например, Java ) не разрешают доступ к памяти с использованием адресов. Кроме того, безопасный диалект C Циклон решает многие проблемы с указателями. См. язык программирования C для более подробного обсуждения.
void
указатель или void*
поддерживается в ANSI C и C ++ как общий тип указателя.. Указатель на недействителен
может хранить адрес любого объекта (не функции) и, в C, неявно преобразуется в другой тип указателя объекта при назначении, но при любом разыменовании он должен быть явно приведен. KR C использовал char *
для цели «указатель, не зависящий от типа» (до ANSI C).
int x = 4; void * p1 = x; int * p2 = p1; // void * неявно преобразуется в int *: допустимый C, но не C ++ int a = * p2; int b = * (int *) p1; // при разыменовании inline неявное преобразование отсутствует
C ++ не позволяет неявное преобразование void *
в других типах указателей, даже в присваиваниях. Это было дизайнерское решение, чтобы избежать неосторожных и даже непреднамеренных приведений, большинства компиляторов выводят только предупреждения, а не ошибки при обнаружении других приведений.
int x = 4; void * p1 = x; int * p2 = p1; // это не удается в C ++: нет неявного преобразования из void * int * p3 = (int *) p1; // приведение в стиле C int * p4 = static_cast(p1); // C ++ приведение
В C ++ нет void
(ссылка на void) для дополнения void *
(указатель на void), потому что ссылки ведут себя как псевдонимы Эти объявления указателя охватывают большинство вариантов объявления указателя, поскольку они указывают на то, что может быть использовано как тип void
.
. Конечно, можно иметь тройные указатели, но основные принципы, лежащие в основе тройного указателя, уже существуют в двойном указателе.
char cff [5] [5]; / * массив массивов символов * / char * cfp [5]; / * массив указателей на символы * / char ** cpp; / * указатель на указатель на char ("двойной указатель") * / char (* cpf) [5]; / * указатель на массив (ы) символов * / char * cpF (); / * функция, возвращающая указатель на char (s) * / char (* CFp) (); / * указатель на функцию, которая возвращает символ * / char (* cfpF ()) [5]; / * функция, возвращающая указатель на массив символов * / char (* cpFf [5]) (); / * массив указателей на функции, которые возвращают символ * /
() и имеют более высокий приоритет, чем *.
В языке программирования C # указатели поддерживаются только при определенных условиях: любой блок кода, включая указатели, должны быть помечен знаком небезопасно
ключевое слово. Такие блоки обычно требуют более высоких разрешений безопасности для запуска. Синтаксис по существу такой же, как в C ++, и может быть адресом может быть либо управляемой, либо неуправляемой памятью. Однако указатели на управляемую память (любой указатель на управляемый объект) должны быть объявлены с использованием ключевого слова фиксированное
, которое не позволяет сборщику мусора перемещать объект как часть управления памятью, пока указатель находится в области видимости, таким образом, адрес указателя остается действительным.
Исключение использование структуры IntPtr
, которая является безопасным управляемым средством int *
и не требует небезопасного кода. Этот тип часто возвращается при использовании методов из System.Runtime.InteropServices
, например:
// Получить 16 байт памяти из неуправляемой памяти процесса IntPtr pointer = System.Runtime.InteropServices.Marshal.AllocHGlobal (16) ; // Что-то делаем с выделенной памятью // Освобождаем выделенную память System.Runtime.InteropServices.Marshal.FreeHGlobal (pointer);
.NET framework включает множество классов и методов в пространствах имен System
и System.Runtime.InteropServices
(например, Marshal
class), которые преобразуют типы.NET (например, System.String
) для многих неуправляемых типов и указателей (например, LPWSTR
или void *
), чтобы разрешить связь с неуправляемым кодом. Большинство таких методов имеют те же требования к разрешению безопасности, что и неуправляемый код, поскольку они могут влиять на произвольные места в памяти.
Язык программирования COBOL поддерживает указатели на переменные. Примитивные или групповые (записи) объекты данных, объявленные в РАЗДЕЛ СВЯЗИ
программы, по своей природе основаны на указателях, где единственная память, выделенная в программе, - это пространство для адресации элемента данных (обычно одна память слово). В исходном коде программы эти элементы данных используются так же, как и переменные РАБОЧЕЕ ХРАНИЛИЩЕ
, но к их содержимому неявно осуществляется косвенный доступ через их указатели СВЯЗЬ
.
Пространство памяти для каждого объекта данных обычно выделяется динамически с использованием внешних операторов ВЫЗОВ
или встроенных расширенных языковых конструкций, таких как Операторы 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, и т. д. eatures.
Fortran-90 представил возможность строго типизированного указателя. Указатели Fortran содержат больше, чем просто адрес памяти. Они также инкапсулируют нижнюю и верхнюю границы размеров массива, шаги (например, для поддержки произвольных разделов массива) и другие метаданные. Оператор ассоциации =>
используется для связывания POINTER
с переменной, имеющей атрибут TARGET
. Оператор Fortran-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 читать (1, iostat = ioerr) real_list_temp% sample_data if (ioerr / = 0) exit allocate (real_list_temp% next) real_list_temp =>real_list_temp =>real_list_temp =>end do
Fortran-2003 добавляет поддержку указателей процедур. Кроме того, как часть функциональной совместимости C, Fortran-2003 поддерживает встроенные функции для преобразования указателей в стиле C в указатели Fortran и обратно.
Go имеет указатели. Его синтаксис объявления эквивалентен синтаксису C, но написан наоборот, заканчивая типом. В отличие от C, Go имеет сборку мусора и запрещает арифметику указателей. Ссылочных типов, как в C ++, не существует. Некоторые встроенные типы, такие как карты и каналы, помещаются в рамки (т.е. внутри они являются указателями на изменяемые структуры) и инициализируются с помощью функции make
. В подходе к унифицированному синтаксису между указателями и не указателями оператор стрелки (->
) был опущен: оператор точки на указателе ссылается на поле или метод разыменованного объекта. Однако это работает только с 1 уровнем косвенного обращения.
В отличие от C, C ++ или Pascal, в Java нет явного представления указателей. Вместо этого более сложные структуры данных, такие как объекты и массивы, реализуются с использованием ссылок. Язык не предоставляет никаких явных операторов манипулирования указателями. Однако код по-прежнему может попытаться разыменовать нулевую ссылку (нулевой указатель), что приведет к возникновению исключения времени выполнения. Пространство, занятое объектами памяти, на которые нет ссылок, автоматически восстанавливается сборкой мусора во время выполнения.
Указатели реализованы во многом так же, как и в Паскале, как и VAR
параметры в вызовах процедур. Modula-2 даже более строго типизирован, чем Паскаль, с меньшим количеством способов выхода из системы типов. Некоторые из вариантов Modula-2 (например, Modula-3 ) включают сборку мусора.
Как и в Modula-2, доступны указатели. По-прежнему существует меньше способов обойти систему типов, поэтому Оберон и его варианты по-прежнему более безопасны в отношении указателей, чем Модула-2 или его варианты. Как и в случае с Modula-3, сборка мусора является частью спецификации языка.
В отличие от многих языков, в которых есть указатели, стандартный ISO Паскаль позволяет указателям ссылаться только на динамически созданные переменные, которые являются анонимными и не позволяют для ссылки на стандартные статические или локальные переменные. У него нет арифметики указателей. Указатели также должны иметь связанный тип, а указатель на один тип несовместим с указателем на другой тип (например, указатель на char несовместим с указателем на целое число). Это помогает устранить проблемы безопасности типов, присущие другим реализациям указателей, особенно тем, которые используются для PL / I или C. Это также устраняет некоторые риски, вызванные висячими указателями, но позволяет динамически освобождать ссылочное пространство с помощью стандартной процедуры dispose
(которая имеет тотже эффект, что и free
библиотечная функция, найденная в C ) означает, что риск зависания указателей полностью не устранен.
Однако в некоторых коммерческих реализациях компилятора Pascal (или производных) с открытым исходным кодом - например Free Pascal, Turbo Pascal или Object Pascal в Embarcadero Delphi - указатель может ссылаться на стандартные статические или локальные переменные и может быть приведен от одного типа указателя к другому. Более того, арифметика указателей не ограничена: добавление или вычитание из указателя перемещает его на указанное количество байтов в любом направлении, но при использовании стандартных процедур Inc
или Dec
указатель перемещается на размер типа данных, на который он объявлен. Нетипизированный указатель также предоставляется под именем Pointer
, который совместим с другими типами указателей.
Язык программирования Perl поддерживает указатели, хотя и редко используемые, в форме функций pack и unpack. Они предназначены только для простого взаимодействия с скомпилированными библиотеками ОС. Во всех остальных случаях Perl использует ссылки, которые являются типизированными и не допускают никаких форм арифметики указателей. Они используются для создания сложных структур данных.
Викискладе есть средства массовой информации, связанные с указателями (вычисления). |
Викиверситет имеет учебные ресурсы по указателям |
В Wikibook Программирование на C есть страница по теме: Указатели |