Указатели совместно с адресной арифметикой играют в Си особую роль. Можно сказать, что они определяют лицо языка. Благодаря им Си может считаться одновременно языком высокого и низкого уровня по отношению к памяти.
Если говорить о понятиях указатель, ссылка, объект, то они встречаются не только в языках программирования, но в широком смысле в информационных технологиях. Когда речь идет о доступе к информационным ресурсам, то существуют различные варианты доступа к ним:
· копия (значение, объект) – пользователь получает точную копию информационного ресурса в момент доступа к ней (например, копию файла, таблицы базы данных и т. п.). Он может как ему угодно изменять его содержимое, что не отражается на оригинале;
· указатель – адресная информация о расположении информационного ресурса, через которую пользователь может обратиться к нему. При изменении содержимого объекта через указатель на него всегда возникает проблема синхронизации (разделения) ресурса между несколькими пользователями, имеющими адресную информацию о нем. Синонимом указателя в информационных технологиях является ссылка. Иногда она имеет все внешние признаки объекта, например, ярлык файла на рабочем столе, который внешне выглядит как файл, а на самом деле ссылается на файл-оригинал.
В языках программирования термины объект (значение), указатель и ссылка имеют примерно аналогичный смысл, но касаются способов доступа и передачи значений переменных.
· терминология ссылка, значение касается фундаментальных свойств переменных в языках программирования. Имя переменной в различных контекстах может восприниматься как ее значение (содержимое памяти), так и ссылка на нее (адрес памяти, указатель)(см. 1.3 ). Например, при присваивании левая часть рассматривается как ссылка, а правая – как значение (см. 1.4 );
· при передаче формальных параметров при вызове процедур (функций) практически во всех языках программирования реализованы способы передачи по ссылке и по значению;
· в Паскале и Си определено понятие указатель как переменная особого вида, содержащая адрес размещения в памяти другой переменной. Использование указателей позволяется создавать динамические структуры данных, в которых элементы взаимно ссылаются друг на друга;
· и, наконец, в Си существует расширенная интерпретация указателя, именуемая адресной арифметикой, которая позволяет интерпретировать значение любого указателя как адрес не отдельной переменной, а памяти в целом, где она размещена.
Указатель в Си
Передавать данные между программами, данные от одной части программы к другой (например, от вызывающей функции к вызываемой) можно двумя способами :
· создавать в каждой точке программы (например, на входе функции) копию тех данных, которые необходимо обрабатывать ;
· передавать информацию о том, где в памяти расположены данные. Такая информация, естественно, является более компактной, чем сами данные, и ее условно можно назвать указателем. Получаем «дилетантское» определение указателя. указатель — переменная, содержащая информацию о расположении в памяти другой переменной.
Наряду с указателем в программировании также используется термин ссылка. Ссылка – содержанием ссылки также является адресная информация об объекте (переменной), но внешне она выглядит как переменная (синоним оригинала).
Указатель как элемент архитектуры компьютера. Указатели занимают особое место среди типов данных, потому что они проецируют на язык программирования ряд важных принципов организации обработки данных в компьютере. Понятие указателя связано с такими понятиями компьютерной архитектуры как адрес, косвенная адресация, организация внутренней (оперативной) памяти. От них мы и будем отталкиваться. Внутренняя (оперативная) память компьютера представляет собой упорядоченную последовательность байтов или машинных слов (ячеек памяти), проще говоря — массив. Номер байта или слова памяти, через который оно доступно как из команд компьютера, так и во всех других случаях, называется адресом. Если в команде непосредственно содержится адрес памяти, то такой доступ этому слову памяти называется прямой адресацией .
Возможен также случай, когда машинное слово содержит адрес другого машинного слова. Тогда доступ к данным во втором машинном слове через первое называется косвенной адресацией. Команды косвенной адресации имеются в любом компьютере и являются основой любого регулярного процесса обработки данных. То же самое можно сказать о языке программирования. Даже если в нем отсутствуют указатели, как таковые, работа с массивами базируется на аналогичных способах адресации данных.
В языках программирования имя переменной ассоциируется с адресом области памяти, в которой транслятор размещает ее в процессе трансляции программы. Все операции над обычными переменными преобразуются в команды с прямой адресацией к соответствующим словам памяти.
Соответственно, основная операция для указателя — это косвенное обращение по нему к той переменной, адрес которой он содержит. В Си имеется специальная операция * — звездочка, которую называют косвенным обращением по указателю. В более широком смысле ее следует понимать как переход от переменной-указателя к той переменной (объекту), на которую он ссылается. В дальнейшем будем пользоваться такими терминами:
· указатель, который содержит адрес переменной, ссылается на эту переменную или назначен на нее;
· переменная, адрес которой содержится в указателе, называется указуемой переменной.
Последовательность действий при работе с указателем включает 3 шага:
1. Определение указуемых переменных и переменной-указателя. Для переменной-указателя это делается особым образом.
int a, x; // Обычные целые переменнные
int *p; // Переменная — указатель на другую целую переменную
В определении указателя присутствует та же самая операция косвенного обращения по указателю. В соответствии с принципами контекстного определения типа переменной (см. 5.5) эту фразу следует понимать так: переменная p при косвенном обращении к ней дает переменную типа int . То есть свойство ее – быть указателем, определяется в контексте возможного применения к ней операции *. Обратите внимание, что в определении присутствует указуемый тип данных. Это значит, что указатель может ссылаться не на любые переменные, а только на переменные заданного типа, то есть указатель в Си типизирован.
2. Связывание указателя с указуемой переменной. Значением указателя является адрес другой переменной. Следующим шагом указатель должен быть настроен, или назначен на переменную, на которую он будет ссылаться.
p = &a; // Указатель содержит адрес переменной a
Операция & понимается буквально как адрес переменной, стоящей справа от нее. В более широкой интерпретации она «превращает» объект в указатель на него (или производит переход от объекта к указателю на него) и является в этом смысле прямой противоположностью операции *, которая «превращает» указатель в указуемый объект. То же самое касается типов данных. Если переменная a имеет тип int , то выражение & a имеет тип – указатель на int или int *.
3. И наконец, в любом выражении косвенное обращение по указателю интерпретируется как переход от него к указуемой переменной с выполнением над ней всех далее перечисленных в выражении операций.
* p =100; // Эквивалентно a =100
x = x + *p; // Эквивалентно x = x + a
(* p )++; // Эквивалентно a ++
Замечание: при обращении через указатель имя указуемой переменной в выражении отсутствует. Поэтому можно считать, что обращение через указатель производится к «безымянной» переменной, а операцию «*» называются также операцией разыменования указателя.
Указатель дает «степень свободы » или универсальности любому алгоритму обработки данных. Действительно, если некоторый фрагмент программы получает данные непосредственно в некоторой переменной, то он может обрабатывать ее и только ее. Если же данные он получает через указатель, то обработка данных (указуемых переменных) может производиться в любой области памяти компьютера (или программы). При этом сам фрагмент может и «не знать», какие данные он обрабатывает, если значение самого указателя передано программе извне.
Адресная арифметика и управление памятью
Способность указателя ссылаться на «отдельно стоящие» переменные не меняет качества языка, поскольку нельзя выйти за рамки множества указуемых переменных, определенных в программе. Такая же концепция указателя принята, например, в Паскале. Но в Си существует еще одна, расширенная интерпретация, позволяющая через указатель работать с массивами и с памятью компьютера ни низком (архитектурном) уровне без каких-либо ограничений со стороны транслятора. Это «свобода самовыражения» обеспечивается одной дополнительной операцией адресной арифметики. Но сначала определим свойства указателя в соответствии с расширенной интерпретацией.
Любой указатель в Си ссылается на неограниченную в обе стороны область памяти (массив), заполненную переменными указуемого типа с индексацией элементов относительно текущего положения указателя.
Такие свойства указателя обеспечиваются адресной арифметикой, которая базируется на нестандартной интерпретации операции указатель+целое и других, производных от нее операциях:
· любой указатель потенциально ссылается на неограниченную в обе стороны область памяти, заполненную переменными указуемого типа;
· переменные в области нумеруются от текущей указуемой переменной, которая получает относительный номер 0. Переменные в направлении возрастания адресов памяти нумеруются положительными значениями 1,2,3. убывания — отрицательными — -1,-2..;
· результатом операции указатель+i является адрес i-ой переменной (значение указателя на i-ую переменную) в этой области относительно текущего положения указателя.