Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Разработка ПО на языке Си для микроконтроллера AT91SAM7S.pdf
Скачиваний:
120
Добавлен:
18.05.2014
Размер:
838.69 Кб
Скачать

3Указатели Указатель – это особый тип данных, который хранит адрес ячейки памяти,

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

Переменная-указатель определяется с использованием символа «*» («звёздочка»), следующего за указанием типа и перед именем указателя (см. листинг 5).

Листинг 5. Примеры определения указателей.

unsigned int *intPtr;// Указатель на переменную типа unsigned int.

// Считается, что intPtr имеет тип 'unsigned int *' const char *str = "Text"; // Указатель на переменную типа char,

// через который невозможно изменить её //значение (указатель на константу).

3.1Разыменование указателя и получение адреса

Использование указателей тесно связано с применением двух операций: разыменование указателя и получение адреса переменной.

3.1.1Операции разыменования указателя и получение адреса переменной

Разыменованием указателя называется конструкция, позволяющая получить доступ к значению переменной, на которую ссылается указатель (т. е. адрес которой хранит указатель). Это может потребоваться в случае, если необходимо изменить или прочитать значение переменной через указатель. Разыменование указателя осуществляется с использованием символа «*» («звёздочка») перед именем указателя.

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

13

Примеры применения операция разыменования указателей и получения адреса переменных приведены в листинге 6.

Листинг 6. Примеры разыменования указателей и получения адреса

//Определяем целочисленную переменную со значением 200 unsigned int value = 200;

//Определение указателя на переменную типа unsigned int

//с одновременной инициализацией адресом переменной value. unsigned int *pointerToValue = &value;

//Теперь pointerToValue указывает на ячейку памяти,

//начиная с которой хранится переменная value.

//Создаём ещё одну переменную с одновременной инициализацией

//значением переменной, на которую указывает pointerToValue unsigned int value2 = *pointerToValue; // Здесь value2 будет равно 200

//Используем разыменование для изменения значения переменной value *pointerToValue = 100;

//Теперь value = 100, а value2 по прежнему 200.

//Возможно поменять значение указателя (сделать так, чтобы он

//ссылался на другую переменную)

pointerToValue = &value2;

// Теперь (*pointerToValue) равно 200

3.1.2Инициализация и присваивание указателей

Одним из способов изменения значения указателя является присвоение ему адреса некоторой переменной. Также часто применяется инициализация (или присваивание) специальным макросом NULL, что делает указатель нулевым. Нулевой указатель – это указатель на адрес памяти 0. Это специальное значение, показывающее, что указатель не указывает ни на какую переменную.

Разыменовывать нулевой указатель нельзя – поведение программы в таком случае непредсказуемо.

3.1.3 Указатели и область действия (время существования) переменных

При выполнении операции получения адреса переменной следует

14

учитывать, как долго эта переменная существует в памяти. Особую осторожность следует проявлять при получении адреса локальных переменных. Такие переменные обычно существуют на стеке и при выходе из функции уничтожаются, после чего на их месте могут оказаться другие данные. Следовательно, разыменование указателя на локальную переменную после выхода из функции, в которой она была определена, может привести к совершенно неожиданному поведению программы. Пример такой ситуации приведён в листинге 7.

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

Листинг 7. Пример ошибки при разыменовании указателя на несуществующую переменную (не делайте так!)

//ВНИМАНИЕ! Программа в данном примере является некорректной. Это

//сделано намеренно для иллюстрации ошибки, связанной с

//разыменованием указателя на несуществующую переменную.

//Объявление функции

void function2 (int *ptr);

// Объявляем функцию, возвращающую указатель на переменную типа int int * function1 (void)

{

int k = 100500;

 

int *pointer = &k;

// Указатель, ссылающийся на локальную

переменную.

 

function2 (pointer);

// Нет ошибки, т. к. переменная k ещё

существует.

 

return pointer;

// НЕЛЬЗЯ!

 

// Переменная, на которую ссылается указатель

 

// будет уничтожена при выходе из функции.

 

// Возвращённый указатель нельзя

}

// разыменовывать!

 

void function2 (int *ptr)

{

//Увеличить значение переменной, на которую ссылается

//указатель, на 100

*ptr = *ptr + 100;

15

}

int main (void)

{

//Создаём указатель, и инициализируем его адресом

//уже уничтоженной переменной! Последующее разыменование приведёт

//к непредсказуемым последствиям.

int * pointer = function1();

//Вызов функции, которая изменяет значение переменной через

//указатель. Последствия непредсказуемы (в лучшем случае -

//зависание или сброс микроконтроллера, в худшем – продолжение

//работы программы с последующим возникновением «необъяснимых»

//ошибок в других местах)

function2 (pointer);

// ... продолжение программы

}

16