Я считаю, что операторы присваивания и переменные-указатели относятся к «самым ценным сокровищам» информатики.
Дональд Кнут , Структурное программирование, с переходом к утверждениям [1]
В информатике указатель — это объект во многих языках программирования , который хранит адрес памяти . Это может быть другое значение, расположенное в памяти компьютера , или, в некоторых случаях, значение отображаемого в памяти компьютерного оборудования . Указатель ссылается на место в памяти, и получение значения, хранящегося в этом месте, называется разыменованием указателя. По аналогии, номер страницы в указателе книги можно рассматривать как указатель на соответствующую страницу; разыменование такого указателя можно выполнить путем перехода к странице с заданным номером страницы и чтения текста, найденного на этой странице. Фактический формат и содержимое переменной-указателя зависят от базовой архитектуры компьютера .
Использование указателей значительно повышает производительность повторяющихся операций, таких как обход повторяющихся структур данных (например, строк , справочных таблиц , управляющих таблиц и древовидных структур). В частности, зачастую гораздо дешевле копировать и разыменовывать указатели по времени и пространству, чем копировать и получать доступ к данным, на которые указывают указатели.
Указатели также используются для хранения адресов точек входа для вызываемых подпрограмм в процедурном программировании и для связывания во время выполнения с библиотеками динамической компоновки (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». Напротив, управление памятью, основанное на разыменовании указателей в некотором приближении к массиву адресов памяти, облегчает рассмотрение переменных как слотов, в которые данные могут быть назначены императивно .
При работе с массивами критическая операция поиска обычно включает в себя этап, называемый вычислением адреса , который включает в себя создание указателя на нужный элемент данных в массиве. В других структурах данных, таких как связанные списки , указатели используются в качестве ссылок для явной связи одной части структуры с другой.
Указатели используются для передачи параметров по ссылке. Это полезно, если программист хочет, чтобы изменения параметра функции были видны вызывающему объекту функции. Это также полезно для возврата нескольких значений из функции.
Указатели также можно использовать для выделения и освобождения динамических переменных и массивов в памяти. Поскольку переменная часто становится избыточной после того, как выполнила свою задачу, ее сохранение является пустой тратой памяти, и поэтому хорошей практикой является ее освобождение (используя исходную ссылку на указатель), когда она больше не нужна. Несоблюдение этого требования может привести к утечке памяти (когда доступная свободная память постепенно или, в тяжелых случаях, быстро уменьшается из-за накопления многочисленных избыточных блоков памяти).
Основной синтаксис определения указателя: [4]
интервал * ПТР ;
Это объявляется ptr
как идентификатор объекта следующего типа:
int
Обычно это выражается более кратко: « ptr
является указателем на int
».
Поскольку язык C не определяет неявную инициализацию для объектов автоматической продолжительности хранения, [5] часто следует проявлять осторожность, чтобы гарантировать, что адрес, на который ptr
указывает, действителен; вот почему иногда предлагается явно инициализировать указатель значением нулевого указателя , которое традиционно указывается в C с помощью стандартизированного макроса NULL
: [6]
интервал * ptr = NULL ;
Разыменование нулевого указателя в C приводит к неопределенному поведению [7] , которое может иметь катастрофические последствия. Однако большинство реализаций просто останавливают выполнение рассматриваемой программы, обычно из-за ошибки сегментации .
Однако ненужная инициализация указателей может затруднить анализ программы и тем самым скрыть ошибки.
В любом случае, как только указатель объявлен, следующим логическим шагом будет его указание на что-то:
интервал а = 5 ; интервал * ptr = NULL ; ПТР = &а a ;
Это присваивает значение a
адреса ptr
. Например, если хранится в ячейке памяти 0x8130, то после присвоения a
значение будет 0x8130. ptr
Для разыменования указателя снова используется звездочка:
* ПТР = 8 ;
Это означает, что нужно взять содержимое адреса ptr
(0x8130), «найти» этот адрес в памяти и установить для него значение 8. Если a
позже к нему снова будет осуществлен доступ, его новое значение будет 8.
Этот пример может быть более понятным, если исследовать память непосредственно. Предположим, что a
он находится по адресу 0x8130 в памяти и ptr
по адресу 0x8134; также предположим, что это 32-битная машина, так что ширина int составляет 32 бита. Ниже показано, что будет в памяти после выполнения следующего фрагмента кода:
интервал а = 5 ; интервал * ptr = NULL ;
(Здесь показан NULL-указатель 0x00000000.) Присвоив a
адрес ptr
:
ПТР = &а a ;
дает следующие значения памяти:
Затем путем разыменования ptr
путем кодирования:
* ПТР = 8 ;
компьютер возьмет содержимое ptr
(0x8130), «найдет» этот адрес и назначит 8 этому местоположению, получив следующую память:
Очевидно, что доступ a
вернет значение 8, поскольку предыдущая инструкция изменила содержимое с a
помощью указателя ptr
.
При настройке структур данных , таких как списки , очереди и деревья, необходимо иметь указатели, помогающие управлять тем, как структура реализуется и контролируется. Типичными примерами указателей являются указатели начала, указатели конца и указатели стека . Эти указатели могут быть либо абсолютными (фактический физический адрес или виртуальный адрес в виртуальной памяти ), либо относительными ( смещение от абсолютного начального адреса («базового»), которое обычно использует меньше битов, чем полный адрес, но обычно требует одного дополнительного указателя. арифметическая операция для решения).
Относительные адреса представляют собой форму ручной сегментации памяти и имеют многие ее преимущества и недостатки. Двухбайтовое смещение, содержащее 16-битное целое число без знака, можно использовать для обеспечения относительной адресации структуры данных размером до 64 КиБ ( 2–16 байт). Его можно легко расширить до 128, 256 или 512 КиБ, если указанный адрес принудительно выровнен по границе полуслова, слова или двойного слова (но, требуя дополнительной побитовой операции «сдвига влево» — на 1, 2 или 3 бита — чтобы скорректировать смещение в 2, 4 или 8 раз перед его добавлением к базовому адресу). Однако в целом такие схемы доставляют много хлопот, и для удобства программиста предпочтительны абсолютные адреса (и лежащее в их основе плоское адресное пространство ).
Смещение в один байт, такое как шестнадцатеричное значение символа ASCII (например, X'29'), может использоваться для указания на альтернативное целочисленное значение (или индекс) в массиве (например, X'01'). Таким образом, символы могут быть очень эффективно преобразованы из « необработанных данных » в пригодный для использования последовательный индекс , а затем в абсолютный адрес без таблицы поиска .
В C индексация массива формально определяется в терминах арифметики указателей; то есть спецификация языка требует, чтобы он array[i]
был эквивалентен *(array + i)
. [8] Таким образом, в C массивы можно рассматривать как указатели на последовательные области памяти (без пробелов), [8] и синтаксис доступа к массивам идентичен синтаксису, который можно использовать для разыменования указателей. Например, массив array
можно объявить и использовать следующим образом:
int массив [ 5 ]; /* Объявляет 5 последовательных целых чисел */ int * ptr = array ; /* Массивы можно использовать в качестве указателей */ ptr [ 0 ] = 1 ; /* Указатели можно индексировать с помощью синтаксиса массива */ * ( array + 1 ) = 2 ; /* Массивы можно разыменовать с помощью синтаксиса указателей */ * ( 1 + массив ) = 2 ; /* Сложение указателей коммутативно */ 2 [ array ] = 4 ; /* Оператор индекса коммутативен */
При этом выделяется блок из пяти целых чисел и присваивается ему имя array
, которое действует как указатель на блок. Другое распространенное использование указателей — это указание на динамически выделенную память из malloc , который возвращает последовательный блок памяти размером не меньше запрошенного, который можно использовать в качестве массива.
Хотя большинство операторов для массивов и указателей эквивалентны, результат оператора sizeof
различается. В этом примере sizeof(array)
будет вычислено 5*sizeof(int)
(размер массива), а sizeof(ptr)
вычислено - sizeof(int*)
размер самого указателя.
Значения массива по умолчанию могут быть объявлены следующим образом:
int массив [ 5 ] = { 2 , 4 , 3 , 1 , 5 };
Если array
он расположен в памяти, начиная с адреса 0x1000 на 32-битной машине с прямым порядком байтов , тогда память будет содержать следующее (значения указаны в шестнадцатеричном формате , как и адреса):
Здесь представлены пять целых чисел: 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.
/* пустой связанный список представлен NULL * или каким-либо другим контрольным значением */ #define EMPTY_LIST NULLструктурная ссылка { void * data ; /* данные этой ссылки */ struct link * next ; /* следующая ссылка; EMPTY_LIST, если его нет */ };
Это определение рекурсивного указателя по существу такое же, как определение рекурсивного указателя из языка программирования Haskell :
Ссылка на данные a = Nil | Минусы а ( ссылка а )
Nil
— это пустой список, а Cons a (Link a)
также ячейка cons типа a
с другой ссылкой также типа a
.
Однако определение со ссылками проверено по типу и не использует потенциально запутанные значения сигналов. По этой причине структуры данных в C обычно обрабатываются через функции-обертки , которые тщательно проверяются на корректность.
Указатели можно использовать для передачи переменных по их адресу, что позволяет изменять их значение. Например, рассмотрим следующий код C :
/* копию 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 вернуть 0 ; }
В некоторых программах необходимый объем памяти зависит от того, что может ввести пользователь . В таких случаях программисту необходимо динамически распределять память. Это делается путем выделения памяти в куче , а не в стеке , где обычно хранятся переменные (хотя переменные также могут храниться в регистрах ЦП). Динамическое выделение памяти может осуществляться только с помощью указателей, а имена, как и в случае с общими переменными, не могут быть заданы.
Указатели используются для хранения и управления адресами динамически выделяемых блоков памяти. Такие блоки используются для хранения объектов данных или массивов объектов. Большинство структурированных и объектно-ориентированных языков предоставляют область памяти, называемую кучей или свободным хранилищем , из которой объекты динамически выделяются.
Пример кода C ниже иллюстрирует, как объекты структуры динамически выделяются и на них ссылаются. Стандартная библиотека C предоставляет функцию malloc()
выделения блоков памяти из кучи. Он принимает размер выделяемого объекта в качестве параметра и возвращает указатель на вновь выделенный блок памяти, подходящий для хранения объекта, или возвращает нулевой указатель, если выделение не удалось.
/* Элемент инвентаря деталей */ struct Item { int id ; /* Номер детали */ char * name ; /* Имя детали */ float Cost ; /* Расходы */ }; /* Выделяем и инициализируем новый объект Item */ struct Item * make_item ( const char * name ) { struct Item * item ; /* Выделяем блок памяти для нового объекта Item */ item = malloc ( sizeof ( struct Item )); if ( item == NULL ) вернуть NULL ; /* Инициализируем члены нового элемента */ memset ( item , 0 , sizeof ( struct Item )); элемент -> идентификатор = -1 ; элемент -> имя = NULL ; предмет -> стоимость = 0,0 ; /* Сохраняем копию имени в новом элементе */ item -> name = malloc ( strlen ( name ) + 1 ); if ( item -> name == NULL ) { free ( item ); вернуть НУЛЬ ; } strcpy ( элемент -> имя , имя ); /* Возвращаем вновь созданный объект Item */ return item ; }
Код ниже иллюстрирует, как объекты памяти динамически освобождаются, т. е. возвращаются в кучу или свободное хранилище. Стандартная библиотека C предоставляет функцию free()
освобождения ранее выделенного блока памяти и возврата его обратно в кучу.
/* Освободить объект Item */ void Destroy_item ( struct Item * item ) { /* Проверить наличие нулевого указателя объекта */ if ( item == NULL ) return ; /* Освободить строку имени, сохраненную в элементе */ if ( item -> name != NULL ) { free ( item -> name ); элемент -> имя = NULL ; } /* Освобождаем сам объект Item */ 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 | 'А' ; }
Управляющие таблицы , используемые для управления ходом выполнения программы , обычно широко используют указатели. Указатели, обычно встроенные в запись таблицы, могут, например, использоваться для хранения точек входа в подпрограммы , которые должны выполняться на основе определенных условий, определенных в той же записи таблицы. Однако указатели могут быть просто индексами на другие отдельные, но связанные таблицы, содержащие массив фактических адресов или сами адреса (в зависимости от доступных конструкций языка программирования). Их также можно использовать для указания на более ранние записи таблицы (как при обработке цикла) или вперед для пропуска некоторых записей таблицы (как при переключении или «раннем» выходе из цикла). Для этой последней цели «указатель» может быть просто номером записи таблицы и может быть преобразован в фактический адрес с помощью простой арифметики.
Во многих языках указатели имеют дополнительное ограничение: объект, на который они указывают, имеет определенный тип . Например, можно объявить указатель, указывающий на целое число ; затем язык попытается помешать программисту указать ему объекты, которые не являются целыми числами, например числа с плавающей запятой , устраняя некоторые ошибки.
Например, в С
int * деньги ; чар * сумки ;
money
будет целочисленным указателем и bags
будет указателем на символ. Следующее приведет к предупреждению компилятора о «назначении из несовместимого типа указателя» в GCC.
сумки = деньги ;
потому что money
и bags
были объявлены с разными типами. Чтобы подавить предупреждение компилятора, необходимо явно указать, что вы действительно хотите выполнить присвоение, приведя его к типу .
сумки = ( символ * ) деньги ;
в котором говорится, что нужно привести целочисленный указатель money
к указателю char и присвоить его bags
.
Проект стандарта C 2005 года требует, чтобы приведение указателя, полученного из одного типа, к одному из другого типа, сохраняло правильность выравнивания для обоих типов (6.3.2.3 Указатели, пар. 7): [9]
char * external_buffer = "abcdef" ; интервал * внутренние_данные ; внутренние_данные = ( int * ) внешний_буфер ; // НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ, если «результирующий указатель // неправильно выровнен»
В языках, допускающих арифметику с указателями, арифметика с указателями учитывает размер типа. Например, добавление целого числа к указателю создает другой указатель, указывающий на адрес, который в это число раз превышает размер типа. Это позволяет нам легко вычислить адреса элементов массива заданного типа, как было показано в примере с массивами 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 )); /* (неопределенное) значение некоторого места в куче */ char * p2 ; /* висячий (неинициализированный) указатель */ * p1 = 'a' ; /* Это нормально, если предположить, что malloc() не вернул NULL. */ * p2 = 'b' ; /* Это вызывает неопределенное поведение */ }
Здесь p2
может указывать на любое место в памяти, поэтому выполнение присваивания *p2 = 'b';
может повредить неизвестную область памяти или вызвать ошибку сегментации .
Там, где в качестве адреса точки входа в программу или запуска функции используется указатель, который ничего не возвращает , а также либо неинициализирован, либо поврежден, если по этому адресу все же осуществляется вызов или переход , возникает " дикая ветвь " Говорят, что это произошло. Другими словами, дикая ветвь — это указатель на функцию, который является диким (висячим).
Последствия обычно непредсказуемы, и ошибка может проявляться по-разному в зависимости от того, является ли указатель «действительным» адресом и существует ли (по совпадению) действительная инструкция (код операции) по этому адресу. Обнаружение дикой ветки может представлять собой одно из самых сложных и разочаровывающих мероприятий по отладке, поскольку большая часть доказательств могла быть уже уничтожена заранее или в результате выполнения одной или нескольких неподходящих инструкций в месте ветвления. Симулятор набора команд, если он доступен, обычно может не только обнаружить дикую ветвь до того, как она вступит в силу, но также обеспечить полную или частичную трассировку ее истории.
Авторелятивный указатель — это указатель, значение которого интерпретируется как смещение от адреса самого указателя; таким образом, если структура данных имеет элемент автоотносительного указателя, который указывает на некоторую часть самой структуры данных, то структура данных может быть перемещена в памяти без необходимости обновлять значение автоотносительного указателя. [12]
В цитируемом патенте также используется термин «относительный указатель», означающий то же самое. Однако значение этого термина использовалось и по-другому:
Базовый указатель — это указатель, значение которого является смещением от значения другого указателя. Это можно использовать для хранения и загрузки блоков данных, присваивая адрес начала блока базовому указателю. [14]
В некоторых языках указатель может ссылаться на другой указатель, поэтому для получения исходного значения требуется несколько операций разыменования. Хотя каждый уровень косвенности может привести к снижению производительности, иногда это необходимо для обеспечения правильного поведения сложных структур данных . Например, в C связный список обычно определяется как элемент, содержащий указатель на следующий элемент списка:
элемент структуры { элемент структуры * следующий ; целое значение ; }; элемент структуры * head = NULL ;
Эта реализация использует указатель на первый элемент списка в качестве заместителя всего списка. Если новое значение добавляется в начало списка, head
его необходимо изменить, чтобы оно указывало на новый элемент. Поскольку аргументы C всегда передаются по значению, использование двойной косвенности позволяет правильно реализовать вставку и имеет желаемый побочный эффект в виде исключения кода специального случая для работы с вставками в начале списка:
// Учитывая отсортированный список в *head, вставьте элемент элемента в первое место, // где все предыдущие элементы имеют меньшее или равное значение. void Insert ( элемент структуры ** head , элемент структуры * элемент ) { элемент структуры ** p ; // p указывает на указатель на элемент for ( p = head ; * p != NULL ; p = & ( * p ) -> next ) { if ( item -> value <= ( * p ) -> value ) перерыв ; } элемент -> следующий = * р ; * р = предмет ; } // Вызывающий абонент делает это: вставить ( & 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 ; интервал ( * fp )( интервал , интервал ); // Указатель функции, который может указывать на такую функцию, как sum fp = & sum ; // fp теперь указывает на функцию sum x = ( * fp )( a , b ); // Вызывает функцию sum с аргументами a и b y = sum ( a , b ); // Вызывает функцию sum с аргументами a и 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 как целое число f = 257 dim как любой ptr g = @f dim как целое число ptr i = g утверждать ( * i = 257 ) утверждать ( ( 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.
Указатель или поддерживается в ANSI C и C++ как универсальный тип указателя void
.void*
Указатель на void
может хранить адрес любого объекта (не функции), [a] и в C неявно преобразуется в любой другой тип указателя объекта при присвоении, но в случае разыменования он должен быть приведен явно. K&R C используется char*
для целей «указателя, не зависящего от типа» (до ANSI C).
интервал х = 4 ; пустота * p1 = & x ; интервал * р2 = р1 ; // void* неявно преобразуется в int*: допустим C, но не C++ int a = * p2 ; int b = * ( int * ) p1 ; // при разыменовании inline неявное преобразование отсутствует
C++ не допускает неявного преобразования в void*
другие типы указателей, даже при присваивании. Это было дизайнерское решение, позволяющее избежать небрежного и даже непреднамеренного приведения типов, хотя большинство компиляторов выводят только предупреждения, а не ошибки, при обнаружении других приведений.
интервал х = 4 ; пустота * p1 = & x ; интервал * р2 = р1 ; // в C++ это не работает: неявного преобразования из void* int * p3 = ( int * ) p1 ; // приведение в стиле C int * p4 = reinterpret_cast < int *> ( p1 ); // Приведение C++
В C++ нет void&
(ссылки на void) для дополнения void*
(указателя на void), поскольку ссылки ведут себя как псевдонимы переменных, на которые они указывают, и никогда не может быть переменной типа void
.
В C++ можно определить указатели на нестатические члены класса. Если класс C
имеет член T a
, то &C::a
это указатель на член a
типа T C::*
. Этот член может быть объектом или функцией . [16] Их можно использовать справа от операторов .*
и ->*
для доступа к соответствующему члену.
структура S { интервал а ; int f () const { return a ;} }; С с1 {}; S * ptrS = & s1 ; int S ::* ptr = & S :: a ; // указатель на S::a int ( S ::* fp )() const = & S :: f ; // указатель на S::f с1 . * ПТР = 1 ; std :: cout << ( s1 . * fp )() << " \n " ; // печатает 1 ptrS ->* ptr = 2 ; std :: cout << ( ptrS ->* fp )() << " \n " ; // печатает 2
Эти объявления указателей охватывают большинство вариантов объявлений указателей. Конечно, возможно иметь тройные указатели, но основные принципы тройного указателя уже существуют в двойном указателе. Здесь используется имя, которому typeid(type).name()
соответствует выражение для каждого из этих типов при использовании g++ или clang . [17] [18]
символ A5_A5_c [ 5 ][ 5 ]; /* массив массивов символов */ char * A5_Pc [ 5 ]; /* массив указателей на символы */ char ** PPc ; /* указатель на указатель на char («двойной указатель») */ char ( * PA5_c ) [ 5 ]; /* указатель на массив(ы) символов */ char * FPcvE (); /* функция, возвращающая указатель на символы char */ char ( * PFcvE )(); /* указатель на функцию, которая возвращает символ */ char ( * FPA5_cvE ())[ 5 ]; /* функция, возвращающая указатель на массив символов */ char ( * A5_PFcvE [ 5 ])(); /* массив указателей на функции, возвращающие символ */
Следующие объявления, включающие указатели на члены, допустимы только в C++:
класс С ; класс Д ; char C ::* M1Cc ; /* указатель на элемент char */ char C ::* A5_M1Cc [ 5 ]; /* массив указателей на член char */ char * C ::* M1CPc ; /* указатель на член для указателя на char(s) */ char C ::** PM1Cc ; /* указатель на указатель на элемент на char */ char ( * M1CA5_c ) [ 5 ]; /* указатель на член массива(ов) символов */ char C ::* FM1CcvE (); /* функция, которая возвращает указатель на элемент char */ char D ::* C ::* M1CM1Dc ; /* указатель на член на указатель на член на указатель на char(s) */ char C ::* C ::* M1CMS_c ; /* указатель на член на указатель на член на указатель на символы */ char ( C ::* FM1CA5_cvE ())[ 5 ]; /* функция, которая возвращает указатель на элемент массива символов */ char ( C ::* M1CFcvE )() /* функция-указатель на элемент, которая возвращает символ */ char ( C ::* A5_M1CFcvE [ 5 ])(); /* массив указателей на функции-члены, которые возвращают символ */
И имеют более высокий приоритет, ()
чем . [19][]
*
В языке программирования C# указатели поддерживаются либо путем маркировки блоков кода, включающих указатели, ключевым словом unsafe
, либо с помощью using
средств System.Runtime.CompilerServices
сборки для доступа к указателям. Синтаксис по существу такой же, как и в C++, а указанный адрес может быть либо управляемой , либо неуправляемой памятью. Однако указатели на управляемую память (любой указатель на управляемый объект) должны быть объявлены с использованием fixed
ключевого слова, которое не позволяет сборщику мусора перемещать указанный объект в рамках управления памятью, пока указатель находится в области видимости, таким образом сохраняя действительный адрес указателя.
Однако исключением является использование IntPtr
структуры, которая является эквивалентом управляемой памяти int*
и не требует unsafe
ни ключевого слова, ни CompilerServices
сборки. Этот тип часто возвращается при использовании методов из класса System.Runtime.InteropServices
, например:
// Получаем 16 байт памяти из неуправляемой памяти процесса IntPtr указатель = System . Время выполнения . ИнтеропСервисы . Маршал . АллокХГлобал ( 16 ); // Делаем что-то с выделенной памятью// Освободить выделенную память System . Время выполнения . ИнтеропСервисы . Маршал . FreeHGlobal ( указатель );
Платформа .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 появилась возможность строго типизированного указателя. Указатели Фортрана содержат больше, чем просто адрес памяти. Они также инкапсулируют нижнюю и верхнюю границы размеров массива, шаги (например, для поддержки произвольных разделов массива) и другие метаданные. Оператор ассоциации используется =>
для связывания a POINTER
с переменной, имеющей TARGET
атрибут. Оператор Fortran-90 ALLOCATE
также может использоваться для связывания указателя с блоком памяти. Например, следующий код может использоваться для определения и создания структуры связанного списка:
тип real_list_t real :: sample_data ( 100 ) тип ( real_list_t ), указатель :: next => null () end type тип ( real_list_t ), цель :: тип my_real_list ( real_list_t ), указатель :: real_list_temp real_list_temp => my_real_list do read ( 1 , iostat = ioerr ) real_list_temp % sample_data if ( ioerr /= 0 ) выйти из выделения ( 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] В нем нет арифметики указателей. У указателей также должен быть связанный тип, и указатель на один тип несовместим с указателем на другой тип (например, указатель на символ несовместим с указателем на целое число). Это помогает устранить проблемы безопасности типов , присущие другим реализациям указателей, особенно тем, которые используются для PL/I или C. Это также устраняет некоторые риски, вызванные висячими указателями , но возможность динамически освобождать ссылочное пространство с помощью dispose
стандартной процедуры (которая имеет тот же эффект, что и free
библиотечная функция, найденная в C ) означает, что риск висячих указателей не был полностью устранен. устранено. [22]
Однако в некоторых коммерческих реализациях компиляторов Паскаля (или его производных) с открытым исходным кодом, таких как Free Pascal , [23] Turbo Pascal или Object Pascal в Embarcadero Delphi , указатель может ссылаться на стандартные статические или локальные переменные и может быть приведен из одной тип указателя на другой. Более того, арифметика указателей не ограничена: добавление или вычитание указателя перемещает его на указанное количество байтов в любом направлении, но использование с ним стандартных процедур Inc
или Dec
перемещает указатель на размер типа данных , на который он, как объявлено , указывает. Под именем также предоставляется нетипизированный указатель Pointer
, который совместим с другими типами указателей.
Язык программирования Perl поддерживает указатели, хотя и редко используемые, в виде функций упаковки и распаковки. Они предназначены только для простого взаимодействия со скомпилированными библиотеками ОС. Во всех остальных случаях Perl использует типизированные ссылки , которые не допускают каких-либо арифметических операций с указателями. Они используются для создания сложных структур данных. [24]
void*
как условно поддерживаемая функция, а стандарт C говорит, что такие преобразования являются «общими расширениями». Это требуется для функции POSIXdlsym
. [15]Указатель Гарольда Лоусона.
Тип массива не содержит дополнительных дырок, поскольку все остальные типы плотно упаковываются при составлении в массивы [на странице 51]
{{cite book}}
: |work=
проигнорировано ( помощь ) .CS1 maint: несколько имен: список авторов ( ссылка ) CS1 maint: числовые имена: список авторов ( ссылка )