- •Конспект лекций Часть 2 Оглавление
- •Часть 2 1
- •8. Указатели
- •8.1. Указатели Понятие указателя
- •Работа с указателями
- •Арифметика указателей
- •Ошибки при работе с указателями
- •If (p1) // Если указатель не равен 0, то все в порядке
- •8.2. Указатели и массивы
- •9. Функции и структура программы
- •9.1. Создание и использование функций Процедурный подход к разработке программ
- •Определение функций в программе
- •Завершение работы функции (инструкция return)
- •If ( Ошибка )
- •Список параметров функций
- •Обращение к функциям в программе
- •Передача данных по значению
- •Передача данных с помощью указателей
- •Передача данных по ссылке
- •Перегружаемые функции
- •Параметры по умолчанию
- •Функции с переменным числом параметров
- •Рекурсивное использование функций
- •Передача функций в качестве параметров
- •Встраиваемые функции (inline - функции)
- •Прототипы функций
- •9.2. Структура программы. Глобальные и локальные данные (области видимости и время жизни) Структура программы
- •Глобальные и локальные данные
- •Классы памяти
- •Доступ к полям структур
- •Указатели на структуры
- •Структурные параметры функций
- •Битовые поля структур
- •10.2. Объединения Обычные объединения
- •Анонимные объединения
- •10.3. Перечисления
- •Void WriteColor (тип_Спектр c )
- •11. Организация ввода/вывода и работа с файлами
- •11.1. Потоки для работы с файлами Общие сведения
- •Пример работы с файлом
- •Создание потока, открытие и закрытие файла
- •Запись и чтение данных в текстовых файлах
- •Запись и чтение данных в двоичном режиме
- •If ( !File ) // Проверили удалось ли открыть файл
- •Как обнаружить конец файла?
- •Прямой доступ при работе с файлами
- •If ( !File ) // Проверили удалось ли открыть файл
- •Статус потоков ввода-вывода
- •Некоторые другие функции управления потоками ввода-вывода
- •Примеры по работе с файлами
- •12. Работа с динамической памятью Распределение памяти при работе программы
- •Динамическое выделение и освобождение памяти в стиле c
- •Возможные ошибки при работе с динамической памятью
- •Динамические массивы
- •Продолжение следует …
Ошибки при работе с указателями
Указатели – это очень мощное, полезное, но и очень опасное средство. Ошибки, которые возникают при неправильном использовании указателей, кроме того, что могут приводить к серьезным и непредсказуемым ошибкам в работе программы, еще и очень трудно диагностировать (обнаруживать).
Основная и наиболее часто встречающаяся ошибка при работе с указателями связана с использованием неинициализированных указателей.
Рассмотрим следующий пример:
int *p1;
*p1 = 1001;
cout << *p1;
Хотя с точки зрения синтаксиса этот фрагмент программы корректен, попытка его выполнения закончится, скорее всего, плачевно. Когда мы определяем указатель (int *p1;), в некотором участке памяти создается обычная переменная – указатель, но поскольку значения этой переменной никакого не присвоено (она не инициализирована), то ее значение будет соответствовать тем случайным данным (“мусору”), которые содержались в этом участке памяти. Таким образом, неинициализированный указатель будет содержать некоторый случайный адрес. Дальнейшие попытки обратиться по этому адресу в память могут привести к одному из двух неприятным последствиям. Если это случайное значение адреса будет указывать на недопустимую область памяти (например, за пределами памяти, выделенной для нашей программы), то возникнет ошибка времени выполнения, и программа аварийно завершит свою работу. Но может быть и хуже. Если случайно значение указателя будет содержать адрес, принадлежащей области памяти нашей программы, то произойдет непредсказуемое изменение данных программы. Она (программа) может продолжить свою работу, а последствия такого несанкционированного изменения в программе могут сказаться значительно позднее и вызвать некорректное поведение программы. Обнаружить причину возникновения подобных ошибок чрезвычайно трудно.
Для того, чтобы минимизировать последствия подобных ошибок, необходимо при определении указателя выполнить его инициализацию. Если заранее не известно, какое конкретное значение должен иметь указатель, то его следует инициализировать нулевым значением:
int *p1 = 0;
По крайне мере в этом случае, если мы в дальнейшем забудем присвоить этому указателю конкретное нужное нам значение, попытка обращения по нулевому адресу обязательно приведет к аварийному завершению работы программы. Такую ошибку найти будет значительно проще, чем искать причину некорректной работы программы.
Более того, при нулевой инициализации указателя перед обращением к этому указателю мы всегда можем выполнить проверку на наличие в указателе конкретного адреса:
If (p1) // Если указатель не равен 0, то все в порядке
{
*p1 = 1001;
cout << *p1;
}
else
// Реакция на ошибочную ситуацию
;
Вторая группа ошибок может быть связана с некорректным использованием арифметики указателей. При некорректном выполнении наращивания или уменьшения указателей с помощью операций + или - можно выйти за пределы предполагаемого объекта (например, массива) и в результате получить неверные данные или модифицировать не те значения.
При выполнении операций инкремента и декремента необходимо помнить, что в результате изменяется значение самого указателя и старое его значение теряется.
Третья группа ошибок относится к использованию операций сравнения > или <. Использование этих операций по отношению к указателям может служить для определения относительного расположения в памяти объектов, на которые ссылаются сравниваемые указатели. Однако такое сравнение имеет смысл, если идет сравнение адресов связанных между собой объектов (например, элементов массива, которые гарантированно упорядочены внутри массива). При сравнении с помощью этих операций указателей на несвязанные между собой переменные результат таких сравнений непредсказуем, так как фактическое расположение в памяти не связанных переменных может отличаться от порядка, в котором эти переменные определялись в программе (это зависит от компилятора).
В любом случае правильное использование указателей полностью определяется знаниями программиста и его внимательностью.