Указатели – камень преткновения

Множество людей при изучении С/С++ поломали зубы именно на указателях. Указатель – это та вещь, которая приближает Си к языкам машинного уровня, так как дает возможность эффективно манипулировать памятью. Давая огромную функциональность и свободу в обращении, указатели таят в себе множество подводных камней, которые способны потопить начинающего программиста и навсегда отбить охоту изучения данного языка.

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

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

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

Поскольку все обьекты в Си имеют свой тип то и указатель должен знать тип обьекта на который он указывает. То есть указатели – тоже типизированная вещь. Обьявить указатель можно следующим образом

int *i

i – указатель на целое и пока он не инициализирован, он ни на что не указывает. Для начала ему нужно присвоить адрес какого нибудь обьекта. То есть сам указатель обьектом не является, но может указывать на другой, существующий обьект, например на целое число.

int *i;
int j;

i = &j;

после такого присваивания i указывает на область памяти, где хранится j. В нашем примере использована операция & – операция взятия адреса.
Что толку, что указатель инициализирован, спросите Вы? Его же теперь надо как-то использовать, а как?

Имея указатель на обьект мы можем получить его значение с помощью операции разыменования. (*i) – есть сам обьект и стоять он может как в правой так и в левой части оператора присваивания.

k = *i; (получили значение, эквивалентно k = j)
*i = k; (поменяли значение, эквивалентно j = k)

Если смотреть на это трезвыми глазами, то можно сказать что смысла в применении указателя в этом случае никакого – он просто дублирует переменную j, не больше того. И это правильно, ведь данный пример несет исключительно образовательную нагрузку, не более. Но уже из него можно выудить интересный вопрос:

А что будет если указателю ничего не присвоить а просто его разыменовать?(тоесть написать k = *i без i = &j)

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

Инициализировать можно не только адресом статического обьекта – можно выделить динамическую память адрес ее начала присвоить указателю.

j = new int;

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

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



Digital Ocean
Провайдер облачного хостинга - заведи свой виртуальный сервер всего за $5 в месяц !

3 Comments

  1. Просто Путник

    К вопросу о
    k = *i; (получили значение)
    *i = k; (поменяли значение)

    В перле есть прикольный момент:там ф-ция получения части строки “обратима”:
    $string = “This is what you have”;
    $first = substr($string, 0, 1); # “T”

    Это обычное использование. А вот наоборот:

    $string = “This is what you have”;
    substr($string, 5, 2) = “wasn’t”; # change “is” to “wasn’t”
    В рез-те получается
    This wasn’t what you have

  2. Aph1na

    Да,Толик, ты прав, указателями можно потопить кого угодно 🙂
    А вообще написано классно. Кстати, как тебе идея сделать блог типа “Спасательный круг дя программиста” или какое-нибудь другое название (могу придумать:)) в котором писать заметки о “подводных камнях” программирования на более доступном языке, чем в книгах, и с рабочими примерами?

  3. tolix

    2 Просто Путник:

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

Leave a Reply