stringtranslate.com

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

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

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

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

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

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

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

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

История

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

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

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

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

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

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

Указатели обычно используются в языках программирования, которые поддерживают прямую манипуляцию памятью, таких как 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-списки , эти языки облегчают рекурсивные средства для построения и обработки данных — например, путем рекурсивного доступа к элементам головы и хвоста списков списков; например, «взять car из cdr из cdr». Напротив, управление памятью, основанное на разыменовании указателя в некотором приближении массива адресов памяти, облегчает обработку переменных как слотов, в которые данные могут быть назначены императивно .

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

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

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

Указатели C

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

int * ptr ; 

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

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

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

int * ptr = NULL ;   

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

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

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

int a = 5 ; int * ptr = NULL ;      птр = a ;  

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

* птр = 8 ;  

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

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

int a = 5 ; int * 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

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

int array [ 5 ]; /* Объявляет 5 смежных целых чисел */ int * ptr = array ; /* Массивы можно использовать как указатели */ ptr [ 0 ] = 1 ; /* Указатели можно индексировать с помощью синтаксиса массива */ * ( array + 1 ) = 2 ; /* Массивы можно разыменовывать с помощью синтаксиса указателя */ * ( 1 + array ) = 2 ; /* Сложение указателя коммутативно */ 2 [ array ] = 4 ; /* Оператор индекса коммутативен */                      

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

Хотя большинство операторов массивов и указателей эквивалентны, результат оператора sizeofотличается. В этом примере sizeof(array)будет оцениваться как 5*sizeof(int)(размер массива), тогда как sizeof(ptr)будет оцениваться как sizeof(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 NULLstruct link { void * data ; /* данные этой ссылки */ struct link * next ; /* следующая ссылка; EMPTY_LIST, если ее нет */ };         

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

 Данные Link a = Ноль | Минусы a ( Link a )         

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 ) return NULL ;           /* Инициализируем элементы нового элемента */ memset ( item , 0 , sizeof ( struct Item )); item -> id = -1 ; item -> name = NULL ; item -> cost = 0.0 ;              /* Сохраняем копию имени в новом элементе */ item -> name = malloc ( strlen ( name ) + 1 ); if ( item -> name == NULL ) { free ( item ); return NULL ; } strcpy ( item -> name , name );                 /* Возвращаем вновь созданный объект Item */ return item ; }  

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

/* Освобождение объекта Item */ void destroy_item ( struct Item * item ) { /* Проверка на наличие пустого указателя объекта */ if ( item == NULL ) return ;           /* Освобождаем строку имени, сохраненную в элементе */ if ( item -> name != NULL ) { free ( item -> name ); item -> name = NULL ; }           /* Освобождаем сам объект Item */ free ( item ); } 

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

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

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

int * hardware_address = ( int * ) 0x7FFF ;    

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

#define VID ((беззнаковый короткий (*)[80])0xB8000)void foo ( void ) { VID [ 4 ][ 1 ] = 0x1F00 | 'A' ; }       

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

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

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

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

Например, в Си

int * деньги ; char * сумки ;  

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

сумки = деньги ;  

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

сумки = ( символ * ) деньги ;   

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

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

char * внешний_буфер = "abcdef" ; int * внутренние_данные ;    internal_data = ( int * ) external_buffer ; // НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ, если «результирующий указатель // выровнен неправильно»     

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

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

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

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

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

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

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

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

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

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

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

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

int func ( void ) { char * p1 = malloc ( sizeof ( char )); /* (неопределенное) значение некоторого места в куче */ char * p2 ; /* висячий (неинициализированный) указатель */ * p1 = 'a' ; /* Это нормально, если malloc() не вернула NULL. */ * p2 = 'b' ; /* Это вызывает неопределенное поведение */ }                  

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

Дикая ветвь

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

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

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

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

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

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

Указатель на основе

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

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

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

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

элемент структуры { элемент структуры * следующий ; значение int ; };       элемент структуры * head = NULL ;    

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

// Учитывая отсортированный список в *head, вставить элемент item в первую позицию, где все предыдущие элементы имеют меньшее или равное значение. void insert ( struct element ** head , struct element * item ) { struct element ** p ; // p указывает на указатель на элемент for ( p = head ; * p != NULL ; p = & ( * p ) -> next ) { if ( item -> value <= ( * p ) -> value ) break ; } item -> next = * p ; * p = item ; }                                  // Вызывающий объект делает следующее: insert ( & head , item ); 

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

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

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

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

int sum ( int n1 , int n2 ) { // Функция с двумя целочисленными параметрами, возвращающая целочисленное значение return n1 + n2 ; }          int main ( void ) { int a , b , x , y ; int ( * fp )( int , int ); // Указатель на функцию, который может указывать на функцию, например sum fp = & sum ; // fp теперь указывает на функцию sum x = ( * fp )( a , b ); // Вызывает функцию sum с аргументами a и b y = sum ( a , b ); // Вызывает функцию sum с аргументами a и b }                         

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

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

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

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

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

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

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

Ада

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

БАЗОВЫЙ

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

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

dim как целое число f = 257 dim как любой ptr g = @ f dim как целое число ptr i = g assert ( * i = 257 ) assert ( ( g + 4 ) = ( @ f + 1 ) )                           

С и С++

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

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

Арифметика указателей , то есть возможность изменять целевой адрес указателя с помощью арифметических операций (а также сравнений величин), ограничена стандартом языка, чтобы оставаться в пределах границ одного объекта массива (или сразу после него), и в противном случае вызовет неопределенное поведение . Добавление или вычитание из указателя перемещает его на величину, кратную размеру его datatype . Например, добавление 1 к указателю на 4-байтовые целочисленные значения увеличит byte-address указателя на 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 для более подробного обсуждения.

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

int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // void* неявно преобразован в int*: допустимо для C, но не для C++ int a = * p2 ; int b = * ( int * ) p1 ; // при разыменовывании inline неявного преобразования не происходит                 

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

int x = 4 ; void * p1 = & x ; int * p2 = p1 ; // это не работает в C++: нет неявного преобразования из void* int * p3 = ( int * ) p1 ; // Приведение в стиле C int * p4 = reinterpret_cast < int *> ( p1 ); // Приведение в C++                  

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

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

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

struct S { int a ; int f () const { return a ;} }; S s1 {}; S * ptrS = & s1 ;           int S ::* ptr = & S :: a ; // указатель на S::a int ( S ::* fp )() const = & S :: f ; // указатель на S::f          s1 . * ptr = 1 ; std :: cout << ( s1 . * fp )() << " \n " ; // печатает 1 ptrS ->* ptr = 2 ; std :: cout << ( ptrS ->* fp )() << " \n " ; // печатает 2              

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

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

char A5_A5_c [ 5 ][ 5 ]; /* массив массивов символов */ char * A5_Pc [ 5 ]; /* массив указателей на символы */ char ** PPc ; /* указатель на указатель на символ ("двойной указатель") */ char ( * PA5_c ) [ 5 ]; /* указатель на массив(ы) символов */ char * FPcvE (); /* функция, возвращающая указатель на символ(ы) */ char ( * PFcvE )(); /* указатель на функцию, возвращающую символ */ char ( * FPA5_cvE ())[ 5 ]; /* функция, возвращающая указатель на массив символов */ char ( * A5_PFcvE [ 5 ])(); /* массив указателей на функции, возвращающие символ */                   

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

class C ; class D ; char C ::* M1Cc ; /* указатель на член на char */ char C ::* A5_M1Cc [ 5 ]; /* массив указателей на член на char */ char * C ::* M1CPc ; /* указатель на член на указатель на символ(ы) */ char C ::** PM1Cc ; /* указатель на указатель на член на char */ char ( * M1CA5_c ) [ 5 ]; /* указатель на член на массив(ы) символов */ char C ::* FM1CcvE (); /* функция, возвращающая указатель на член на char */ char D ::* C ::* M1CM1Dc ; /* указатель на член на указатель на член на указатель на символ(ы) */ 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 pointer = System.Runtime.InteropServices.Marshal.AllocHGlobal ( 16 ) ;   // Сделать что-нибудь с выделенной памятью// Освобождаем выделенную память System.Runtime.InteropServices.Marshal.FreeHGlobal ( pointer ) ;

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

КОБОЛ

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

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

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

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

ПЛ/И

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

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

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

Д

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

Эйфелева

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

Фортран

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

тип real_list_t реальный :: sample_data ( 100 ) тип ( real_list_t ), указатель :: следующий => null () конечный тип           тип ( 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 ) exit  allocate ( real_list_temp % next ) real_list_temp => real_list_temp % next end do             

Fortran-2003 добавляет поддержку указателей процедур. Также, как часть функции взаимодействия с C , Fortran-2003 поддерживает встроенные функции для преобразования указателей в стиле C в указатели Fortran и обратно.

Идти

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

Ява

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

Модула-2

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

Оберон

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

Паскаль

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

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

Перл

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

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

Примечания

  1. ^ Некоторые компиляторы позволяют хранить адреса функций в указателях void. Стандарт C++ перечисляет преобразование указателя функции в void*как условно поддерживаемую возможность, а стандарт C говорит, что такие преобразования являются «обычными расширениями». Это требуется функцией POSIX .dlsym [ 15]

Ссылки

  1. ^ Дональд Кнут (1974). «Структурное программирование с операторами перехода» (PDF) . Computing Surveys . 6 (5): 261–301. CiteSeerX  10.1.1.103.6084 . doi :10.1145/356635.356640. S2CID  207630080. Архивировано из оригинала (PDF) 24 августа 2009 г.
  2. ^ Рейли, Эдвин Д. (2003). Вехи в компьютерной науке и информационных технологиях . Greenwood Publishing Group. стр. 204. ISBN 9781573565219. Получено 2018-04-13 . Указатель Гарольда Лоусона.
  3. ^ "Список наград IEEE Computer Society". Awards.computer.org. Архивировано из оригинала 2011-03-22 . Получено 2018-04-13 .
  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. ^ ab Plauger, PJ ; Brodie, Jim (1992). Справочник программиста по стандартам ANSI и ISO на языке C. Redmond, WA: Microsoft Press. стр. 108, 51. ISBN 978-1-55615-359-4. Тип массива не содержит дополнительных отверстий, поскольку все другие типы плотно упаковываются при составлении в массивы [на странице 51]
  9. ^ WG14 N1124, C – Утвержденные стандарты: ISO/IEC 9899 – Языки программирования – C, 2005-05-06.
  10. ^ Юнг, Ральф. «Указатели сложны II, или: Нам нужны лучшие языковые спецификации».
  11. ^ Юнг, Ральф. «Указатели сложны, или Что в байте?».
  12. ^ Патент США 6625718, Штайнер, Роберт С. (Брумфилд, Колорадо), «Указатели, которые являются относительными к их собственным текущим местоположениям», выдан 2003-09-23, передан Avaya Technology Corp. (Баскинг-Ридж, Нью-Джерси) 
  13. ^ Патент США 6115721, Надь, Майкл (Тампа, Флорида), «Система и метод сохранения и восстановления базы данных с использованием самоуказателей», выдан 05.09.2000, передан IBM (Армонк, Нью-Йорк) 
  14. ^ "Based Pointers". Msdn.microsoft.com . Получено 2018-04-13 .
  15. ^ "CWG Issue 195". cplusplus.github.io . Получено 2024-02-15 .
  16. ^ "Указатели на функции-члены". isocpp.org . Получено 2022-11-26 .
  17. ^ "c++filt(1) - страница руководства Linux".
  18. ^ «ABI Itanium C++».
  19. ^ Ульф Билтинг, Ян Скансхольм, «Vägen до C» (Дорога к C), третье издание, стр. 169, ISBN 91-44-01468-6 
  20. ^ Ник Парланте, [1], Стэнфордская библиотека компьютерного образования, стр. 9–10 (2000).
  21. ^ Стандарт ISO 7185 Pascal (неофициальная копия), раздел 6.4.4 Типы указателей. Архивировано 24 апреля 2017 г. на Wayback Machine и далее.
  22. ^ Дж. Уэлш, В. Дж. Снерингер и К. А. Хоар, «Неоднозначности и неуверенность в Паскале», Программное обеспечение: практика и опыт 7 , стр. 685–696 (1977)
  23. ^ Справочное руководство по языку Free Pascal, раздел 3.4 Указатели
  24. ^ Контактные данные. "// Создание ссылок (ссылки Perl и вложенные структуры данных)". Perldoc.perl.org . Получено 2018-04-13 .

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