Урок 80. Указатели. Введение

   ⁄ 

 Обновлено 15 Сен 2017

  ⁄   

В 10 уроке о переменных, инициализации и присваивании в C++ мы узнали, что переменная — это название кусочка памяти, который содержит значение. При выполнении инициализации переменной, ей автоматически присваивается свободный адрес памяти, и любое значение, которое мы присваиваем переменной, сохраняется в этом адресе памяти.

Например:

При выполнении этого стейтмента процессором, выделяется часть памяти ОЗУ. Для примера предположим, что переменной b присваивается ячейка памяти 150. Всякий раз, когда программа встречает переменную b в выражении или стейтменте, она знает, что для того, чтобы получить значение — нужно посмотреть в ячейке памяти 150.

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

Однако этот подход имеет некоторые ограничения, которые мы обсудим в этом и следующих уроках.

Оператор адреса (&)

Оператор адреса (address-of) & позволяет узнать, какой адрес памяти присвоен переменной. Всё довольно просто:

Результат на компьютере автора:

7
0046FCF0

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

Оператор разыменования (*)

С одним только адресом переменной далеко не заедешь.

Оператор разыменования (*) позволяет получить значение по определенному адресу:

Результат на компьютере автора:

7
0046FCF0
7

Примечание: Хотя оператор разыменования выглядит так же, как и оператор умножения, отличить их можно по тому, что оператор разыменования — унарный, а оператор умножения — бинарный.

Указатели

Теперь, когда мы уже знаем об операторах адреса и разыменования, мы можем поговорить об указателях. Указатель — это переменная, значением которой является адрес памяти.

Тема «Указатели» обычно считается одной из самых запутанных в языке C++, но при правильном подходе с объяснением — всё довольно просто.

Объявление указателя

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

Синтаксически C++ принимает объявление указателя, когда звездочка находится рядом с типом данных, с именем переменной или даже посередине. Обратите внимание, эта звездочка не является оператором разыменования. Это всего лишь часть синтаксиса объявления указателя.

Однако при объявлении нескольких указателей звездочка должна находиться возле каждой переменной. Это легко забыть, если вы привыкли указывать звездочку возле типа данных, а не возле имени переменной!

По этой причине при объявлении указателя мы рекомендуем указывать звездочку рядом с именем переменной.

Совет: При объявлении указателя указывайте звездочку рядом с именем переменной.

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

Присваивание значений указателю

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

Для получения адреса переменной используется оператор адреса:

Приведенное выше можно проиллюстрировать как:

Вот почему указатели имеют такое имя — ptr содержит адрес значения переменной, и можно сказать, что ptr указывает на значение.

Еще очень часто можно увидеть следующее:

Результат на компьютере автора:

003AFCD4
003AFCD4

Тип указателя должен соответствовать типу переменной, на которую он указывает:

Следующее не является допустимым:

Это связано с тем, что указатели могут содержать только адреса, а целочисленный литерал 7 не имеет адреса памяти. Если вы все же сделаете это, то компилятор сообщит вам, что он не может преобразовать целочисленное значение в целочисленный указатель.

C++ также не позволит вам напрямую присваивать адреса памяти указателю:

Оператор адреса возвращает указатель

Стоит отметить, что оператор адреса (&) не возвращает адрес своего операнда в качестве литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого получен из аргумента (например, адрес переменной типа int передастся как адрес указателя на значение типа int).

Например:

Результат в Visual Studio 2013:

int *

Разыменование указателей

Как только у нас есть указатель, указывающий на что-то, мы можем его разыменовать, дабы получить значение, на которое он указывает. Разыменованый указатель — это содержимое адреса памяти, на которое он указывает.

Результат:

0034FD90
5
0034FD90
5

Вот почему указатели должны иметь тип. Без типа указатель не знал бы, как интерпретировать содержимое, на которое он указывает (при разыменовании). Также поэтому и должны совпадать тип указателя и тип переменной. Если они не совпадают, то указатель при разыменовании может неправильно интерпретировать биты (например, вместо типа double применить тип int).

После присваивания одного значения указателю, ему можно присвоить и другое:

Когда адрес значения переменной присвоен указателю, то выполняются следующие утверждения:

  ptr — это то же самое, что и &value;

  *ptr обрабатывается так же, как и value.

Поскольку *ptr обрабатывается так же, как и value, то мы можем присваивать ему значения так, как если бы это была бы обычная переменная! Следующая программа выведет 7:

Разыменование некорректных указателей

Указатели в C++ по своей природе являются небезопасными, а их неправильное использование — один из лучших способов получить сбой в программе.

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

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

Размер указателей

Размер указателя зависит от архитектуры, на которой скомпилирован исполняемый файл: 32-битный исполняемый файл использует 32-битные адреса памяти, следовательно, указатель на 32-битном устройстве занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет занимать 64 бита (8 байтов). И это вне зависимости от того, на что он указывает:

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

Какая польза от указателей?

Сейчас вы можете думать, что указатели являются непрактичными, лишними и т.д. Зачем использовать указатель, если мы можем использовать исходную переменную?

Однако, оказывается, что указатели полезны в следующих случаях:

1. Массивы реализованы с помощью указателей. Они могут использоваться для итерации по массиву (рассмотрим в следующих уроках).

2. Они являются единственным способом динамического распределения памяти в C++ (рассмотрим в следующих уроках). Это, безусловно, самый распространенный вариант использования указателей.

3. Они могут использоваться для передачи большого количества данных в функцию без копирования этих данных (рассмотрим в следующих уроках).

4. Они могут использоваться для передачи одной функции в качестве параметра другой функции.

5. Они используются для достижения полиморфизма при работе с наследованием (рассмотрим в следующих уроках).

6. Они могут использоваться для представления одной структуры/класса в другой структуре/классе, формируя, таким образом, целые цепочки.

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

Итого

Указатели — это переменные, которые содержат адреса памяти. Их можно разыменовать с помощью оператора разыменования (*) для извлечения значений из адресов, которые они хранят. Разыменование указателя, значением которого является мусор, приведет к сбою в вашей программе.

Совет: При объявлении указателя указывайте звездочку рядом с именем переменной.

Тест

1. Какие значения мы получим в результате выполнения следующей программы? Предположим, что это 32-битное устройство, и short занимает 2 байта.

Ответ 1

Значения:

0012FF60
7
0012FF60
7

0012FF60
9
0012FF60
9

0012FF54
3
0012FF54
3

4
2

Краткое объяснение по поводу последней пары: 4 и 2. 32-битное устройство означает, что размер указателя — 32 бита, но sizeof() всегда выводит размер в байтах. 32 бита – это 4 байта. Таким образом, sizeof(ptr) равен 4. Поскольку ptr является указателем на short, то *ptr – это short. Размер short, в этом примере, составляет 2 байта. Таким образом, sizeof(*ptr) равен 2.

2. Что не так со следующим фрагментом кода?

Ответ 2

Последняя строчка не скомпилируется.

*Рассмотрим эту программу детальнее.

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

Во второй строке мы определяем новый указатель с именем ptr и присваиваем ему адрес переменной value. Помним, что в этом контексте звездочка является частью синтаксиса объявления указателя, а не оператором разыменования. Так что и в этой строке всё нормально.

В третьей строке звездочка уже является оператором разыменования, и используется для вытаскивания значения, на которое указывает указатель. Таким образом, эта строка говорит: «Вытаскиваем значение, на которое указывает ptr (целочисленное значение), и переписываем его на адрес этого же значения». А это уже какая-то чепуха — вы не можете присвоить адрес целочисленному значению!

Третья строка должна быть:

Таким образом, мы корректно присваиваем указателю адрес значения переменной.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (7 оценок, среднее: 5,00 из 5)
Загрузка...
Поделиться в:
Подписаться на обновления:

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *