stringtranslate.com

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

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

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

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

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

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

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

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

История

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Указатели C

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

интервал * ПТР ; 

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

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

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

интервал * ptr = NULL ;   

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

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

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

интервал а = 5 ; интервал * ptr = NULL ;      ПТР = ;  

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

* ПТР = 8 ;  

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

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

интервал а = 5 ; интервал * ptr = NULL ;      

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

ПТР = ;  

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

Затем путем разыменования 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

В 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. Разбираем это:

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

Ниже приведен пример определения связанного списка в 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использовать в некоторых ситуациях как более безопасную альтернативу примитивным указателям C. C++ также поддерживает другую форму ссылки, совершенно отличную от указателя, называемую просто ссылкой или ссылочным типом .shared_ptrweak_ptr

Арифметика указателя , то есть возможность изменять целевой адрес указателя с помощью арифметических операций (а также сравнения величин), ограничена стандартом языка, чтобы оставаться в пределах одного объекта массива (или сразу после него) и будет в противном случае вызовите неопределенное поведение . Добавление или вычитание указателя перемещает его на величину, кратную размеру его типа данных . Например, добавление 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может хранить адрес любого объекта (не функции) и в 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::*. Этот член может быть объектом или функцией . [15] Их можно использовать справа от операторов .*и ->*для доступа к соответствующему члену.

структура 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 . [16] [17]

символ 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 ])(); /* массив указателей на функции-члены, которые возвращают символ */                                     

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

С#

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

Модуль-2

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

Оберон

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

Паскаль

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

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

Перл

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

Смотрите также

Рекомендации

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

Внешние ссылки