Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.3.2. Указатели и адресная арифметика.

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

Указатель — это адрес памяти. После инициализации он содержит адрес какой-либо переменной. Значение указателя показывает, где в памяти храните объект, а не что хранится по адресу. Операторный символ * используется для разадресации, то есть выборки содержимого по адресу. Существует обратная операция & взятия адреса. Механизм доступа к переменной с помощью указателя можно представить себе в виде перемещаемого по памяти окошка для просмотра или изменения ее содержимого.

Присвоение указателю адреса какой-либо другой переменной позволяет полу­чить ее значение путем разадресации указателя (*р). Значения адресов зависят от операционной системы, версии компилятора и момента запуска в многозадач­ной системе. После выполнения оператора p=&v указатель р содержит адрес пере­менной v, который компилятор определил для хранения значений этой локаль­ной переменной.

Две операции языка C++ (new и delete) позволяют непосредственно управ­лять временем жизни какой-либо переменной. Переменная может быть создана в любой точке программы с помощью операции new и затем при необходимости уничтожена с помощью операции delete. Пример иллюстрирует применение этих операций. Оператор р = new int; присваивает переменной р адрес начала области памяти длиной в один элемент типа int. Память выделяется динамически из специальной области центрального пула памяти, которая называется heap. Эле­мент занимает sizeof(int) байт. Операция sizeof(arg) возвращает размер в бай­тах своего аргумента arg, который может быть либо типом, либо выражением.

Операция delete освобождает ранее занятую память, возвращая ее в heap. Сам указатель при этом продолжает указывать на тот же участок памяти, но он больше ею не управляет. При освобождении памяти нет необходимости явно указывать ее размер, так как эту информацию сохраняет система при реализации операции new. Характерные значения (Oxcdcdcdcd и Oxdddddddd) определяются спосо­бом работы с динамической памятью, принятым в операционной системе (в дан­ном случае Windows NT). Если программист забывает освободить память, то это остается незамеченным в небольших программах, однако часто приводит к отказам в достаточно серьезных разработках и является плохим стилем программирования.

Наряду с рассмотренными операциями в языках С и C++ существуют две функции для захвата памяти в области heap. Действия по выделению памяти для массива array (из size вещественных) выглядят следующим образом:

float *array; //Адрес начала будущего массива

array=(float*) malloc (sizeof(float)*size); //или

array=(float*) calloc (size, sizeof(float)):

Здесь malloc и callос — функции, прототипы которых объявлены в файле alloc.h. Функция malloc (Nbyte) запрашивает память размером Nbyte байт из об­ласти heap и возвращает указатель на первый байт этого участка. Возвращаемый указатель имеет описатель void*. Это означает, что тип переменной, на которую он может указывать, не определен. Так как мы собираемся присвоить его пере­менной array (указателю на тип float), то необходимо осуществить преобразо­вание с помощью cast-преобразования (float*).

В языке C++ имеется возможность инициализировать указатель путем запро­са памяти прямо в момент объявления переменной. Например,

float *р = new float[n]; //'Инициализация указателя р

Здесь действует некая условность. Эту же операцию можно было бы выпол­нить в два этапа.

float *р; // Объявление указателя р р = new float [n]; // Присвоение указателю р

Важно понять, что в сокращенной записи адрес вновь выделенного участка памяти будет присвоен указателю р, а не содержимому по адресу (*р), как это может показаться при рассмотрении примера. Опыт показывает, что такая трак­товка сначала вызывает затруднения у начинающих, поэтому следует внимательно проанализировать обе формы и постараться запомнить принятую условность.

Указатели, объявленные вне функций или объявленные с описателем static, автоматически инициализируются нулями, как и все глобальные переменные в C++. Указатели, объявленные внутри функции (автоматические переменные), не инициализированы вовсе. Использование такого указателя до присвоения ему осмысленного значения является серьезной ошибкой. Нулевой адрес (NULL или 0) не может быть присвоен указателю никакой из функций или операций динамиче­ского выделения памяти (new, malloc и т. д.). В связи с этим он обычно служит признаком того, что указатель еще не был инициализирован программистом. NULL — это символическая константа, определенная в stdio.h для нулевого указа­теля. Символической константой называется константа, определенная с помо­щью макроподстановки #define. Например, #define NULL (void*)0 определяет сим­волическую константу NULL, которая до компиляции везде будет заменена препроцессором на выражение (void*)0. Константа NULL определена как целый ноль, явно приведенный к типу void*, то есть указателю на произвольный, не­определенный тип.