C Урок 26. Указатели и адреса. Часть 2



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

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

Проект мы также создадим из проекта прошлого урока с именем MYPROG25 и дадим ему имя MYPROG26.

Откроем наш проект в Eclipse, произведём его первоначальную настройку и удалим весь наш код из функции main() за исключением возврата. Функция main() приобретёт вот такой вид

 

int main()

{

   return 0; //Return an integer from a function

}

 

Объявим и проинициализируем переменную, а затем выведем в консоль её значение

 

 

Объявим переменную-указатель такого же типа

 

 

Возьмём адрес у переменной a и присвоим его указателю

 

 

Выведем в консоль значение указателя, это и будет адрес нашей переменной a

 

 

Запустим нашу программу, собрав проект и посмотрим значения самой переменной a, а также указателя на неё, который является и её адресом

 

 

Запустим отладку и узнаем, действительно ли это адрес нашей переменной a.

Для этого поставим вот тут точку останова и выполним код до неё

 

 

Мы видим, что переменные получили свои значения

 

 

И интересно то, что у переменной p_a есть теперь раскрывающая птичка слева, которая нам даёт понять, что данная переменная необычная. Откроем эту птичку

 

 

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

Но мы пойдём глубже. Откроем окно дизассемблирования. Вот здесь мы видим, что значение переменной a ушло в стек со смещением 0x18

 

 

Посмотрим адрес стека

 

 

Получается, что наша переменная a лежит в ячейке памяти с адресом 0x61FF18. Откроем его в окне Memory

 

 

Ничего вам это число не понимает? Так это же наша переменная a! Правда байты следуют в обратном порядке. Ну так оно и должно быть. Байты следуют младшим вперёд.

Мы теперь знаем адрес нашей переменной a без всяких указателей. Но мы помним, как мы смотрели значение указателя в окне переменных. Оно у нас такое и было!

Далее мы берём значение адреса переменной a и укладываем его по другому адресу в стек со смещением 0x1С

 

 

Вот там и будет лежать наш указатель. То есть у нас такая ситуация, что указатель с адресом лежит рядом с самим адресом, на который он указывает, в ячейке с адресом 0x61FF1С.

Посмотрим это в памяти

 

 

Вот он, рядышком, и тоже перевёрнутый.

 

 

Так что, отладка — великая сила!

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

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

А сделаем мы это, не заводя никаких лишних переменных, просто разыменуем указатель сразу в аргументе функции printf и выведем его значение в консоль

 

 

Посмотрим результат нашего разыменования

 

 

Всё отлично!

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

По идее, с отладкой всё понятно, но здесь я не удержался и всё-таки покажу вам операцию разыменования в дизассемблере

 

 

Процессор здесь сначала берёт значение адреса, которое у нас хранится в стеке, а затем по адресу, хранящемуся в регистре eax забирает значение, хранящееся по этому адресу, и записывает его в тот же eax. Вот это экономия! Даже не подумаешь, что так можно делать. По идее, как вообще выполняется такая инструкция, в голове не умещается. Это же одна элементарная ячейка памяти в самом процессоре. А инструкция такая существует и имеет свой машинный опкод. Можно его посмотреть. Вызовем контекстное меню в окне дизассемблера и заставим его показывать опкоды

 

 

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

 

 

Вот и всё разыменование.

Ну ладно, делу время потехе час.

Продолжим.

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

 

 

 

Запустим наш код и посмотрим результат

 

 

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

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

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

Добавим ещё одну переменную и покажем её значение в консоли

 

 

Присвоим теперь её адрес нашему указателю

 

 

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

 

 

Запустим наш код

 

 

Всё работает. Указатель наш теперь показывает на место в памяти, где хранится переменная b.

Поработаем с массивом.

Объявим и проинициализируем массив

 

 

Зная то, что имя массива — это его указатель в памяти, покажем значение этого указателя в консоли

 

 

Соберём наш код и посмотрим значение этого указателя, который и является адресом начала массива в памяти, то есть того места, где хранится его нулевой элемент

 

 

Проверим это в отладке

 

 

Мы видим здесь и адрес в памяти и значения всех элементов массива.

Ну и, никак нельзя без дизассемблера. После него уже материал ложится в голову навечно

 

 

Найдём теперь наш массив в памяти. У нас немного изменился адрес стека

 

 

Найдём данную область памяти и посмотрим значения в ней

 

 

Каким образом изменить формат показа значений с четырёхбайтного на однобайтовый, я показал в видеоверсии урока.

Итак, мы нашли наш массив в памяти.

Попробуем присвоить адрес массива другому указателю. Сделать это можно примерно вот так

 

 

Мы взяли адрес у нулевого элемента массива — это и есть адрес массива в памяти и создали на него указатель p_uch.

Теперь попробуем воспользоваться данным указателем, как именем массива и попробуем вывести элементы массива, пользуясь именем указателя на него

 

 

Посмотрим результат наших операций

 

 

Всё отлично! Мы можем пользоваться именем указателя на массив как и самим массивом, ну и наоборот тоже. Это подтвердил предыдущий код.

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

 

 

Как мы помним, мы используем в данном случае две звёздочки. Также может быть и указатель на указатель на указатель и так далее. Но это редко используется. Ну уж про пять звёзд я не слышал точно, но такое вполне возможно.

Присвоим указателю p_a адрес переменной a, а то, помнится, он хранит у нас адрес переменной b

 

 

А указателю p_p_a присвоим адрес указателя p_a

 

 

Выведем значения в консоль. Это будет наш указатель на указатель, затем он же но разыменованный и он же, но дважды разыменованный

 

 

Посмотрим, что из этого получилось

 

 

А получилось всё отлично!

Мы добрались через указатель на указатель до значения самой переменной a. Применив двойное разыменование, мы всё-таки получили возраст третьего человека, адрес которого знает второй человек, адрес которого знает первый человек у этого самого первого человека. Мы даже не будем ничего смотреть в отладке, но воообще было бы интересно, жаль что время не резиновое. Но вам я советую всё же посмотреть.

Теперь попробуем изменить значение переменной a через указатель на указатель на неё и выведем после этого в консоль значение переменной a

 

 

Посмотрим результат

 

 

Всё отлично! Мы изменили значение переменной a, достучавшись до неё по иерархии через два адреса.

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

Так что вас ждёт ещё много интересного!

Всем спасибо за внимание!

 

 

Предыдущая часть Программирование на C Следующий урок

 

Исходный код

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

C Указатели и адреса

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

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

*