как узнать адрес переменной c
Переменные, адреса и указатели
Переменная — это область памяти, к которой мы обращаемся за находящимися там данными, используя идентификатор (в данном случае, имя переменной). При этом у этой помеченной именем области есть еще и адрес, выраженный числом и понятный компьютерной системе. Этот адрес можно получить и записать в особую переменную. Переменную, содержащую адрес области памяти, называют указателем.
Когда мы меняем значение обычной переменной, то, можно сказать, просто удаляем из конкретной области памяти данные и записываем туда новые. Когда мы меняем значение переменной-указателя, то начинаем работать с совершенно иным участком памяти, т.к. меняем содержащийся в ней адрес.
Тема указателей тесно связана с темой динамических типов данных. Когда программа компилируется, то под объявленные переменные так или иначе (в зависимости от того, где они были объявлены) выделяются участки памяти. Потом размер этих участков не меняется, может меняться только их содержимое (значения или данные). Однако именно с помощью указателей можно захватывать и освобождать новые участки памяти уже в процессе выполнения программы. Динамические типы данных будут рассмотрены позже.
Прежде чем перейти к рассмотрению объявления и определения переменных-указателей, посмотрим, что из себя представляет адрес любой переменной и как его получить.
В результате выполнения данного программного кода на экране появляется примерно следующее (шестнадцатеричное число у вас будет другим):
Как мы видим, несмотря на то, что значения переменных поменялись, ячейки памяти остались прежними.
Зная адрес, можно получить значение, которое находится по этому адресу, поставив знак * перед адресом:
На экране будет выведено число 8.
Однако запись типа &a не всегда возможна или удобна. Поэтому существует специальный тип данных — указатели, которым и присваивается адрес на область памяти.
Указатели в языке C, как и другие переменные, являются типизированными, т.е. при объявлении указателя надо указывать его тип. Как мы узнаем позже, с указателями можно выполнять некоторые арифметические операции, и именно точное определение их типа позволяет протекать им корректно. Чтобы объявить указатель, надо перед его именем поставить знак *. Например:
Обратите внимание на то, что в данном случае * говорит о том, что объявляется переменная-указатель. Когда * используется перед указателем не при его объявлении, а в выражениях, то обозначает совсем иное — «получить значение (данные) по адресу, который присвоен указателю». Посмотрите на код ниже:
С помощью комментариев указаны текущие значения ячеек памяти. Подробно опишем, что происходит:
Под сам указатель (там, где хранится адрес) также должна быть выделена память. Объем этой памяти можно узнать с помощью функции sizeof() :
Под указатели всех типов выделяется одинаковый объем памяти, т.к. размер адреса не зависит от типа, а зависит от вычислительной системы. В таком случае, зачем при объявлении указателя следует указывать тип данных, на который он может ссылаться? Дело в том, что по типу данных определяется, сколько ячеек памяти занимает значение, на которое ссылается указатель, и через сколько ячеек начнется следующее значение.
Если указатель объявлен, но не определен, то он ссылается на произвольный участок памяти с неизвестно каким значением:
Результат (в Ubuntu):
Использование неопределенных указателей в программе при вычислениях чревато возникновением серьезных ошибок. Чтобы избежать этого, указателю можно присвоить значение, говорящее, что указатель никуда не ссылается (NULL). Использовать такой указатель в выражениях не получится, пока ему не будет присвоено конкретное значение:
Результат (в Ubuntu):
В данном случае, если попытаться извлечь значение из памяти с помощью указателя, который никуда не ссылается, то возникает «ошибка сегментирования».
На этом уроке вы должны понять, что такое адрес переменной и как его получить ( &var ), что такое переменная-указатель ( type *p_var; p_var = &var ) и как получить значение, хранимое в памяти, зная адрес ячейки ( *p_var ). Однако у вас может остаться неприятный осадок из-за непонимания, зачем все это надо? Это нормально. Понимание практической значимости указателей придет позже по мере знакомства с новым материалом.
Практически проверьте результат работы всех примеров данного урока, придумайте свои примеры работы с указателями.
Указатели C++, на массив и структуру, ссылки, разыменование, примеры
Итак, небольшая статья на эту страшную и малопонятную новичкам С++ тему.
Допустим, что вы уже знаете, что такое переменная в языке Си/Си++.
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
Так уж сложилось, что такие громадные величины лучше удобнее кодировать в 16ричной системе исчисления. 0х1 до 0х200000000(в моем случае, 8ГБ памяти же)
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
0х1 | 0х2 | 0х3 | 0х4 | . | 0хC0000000 | . | 0х200000000 |
Вот с номерами ячеек программисту ниразу не удобнее работать, поэтому придумали символьные имена для этих ячеек
1 | 2 | 3 | 4 | . | 3221225472 | . | 8589934592 |
0х1 | 0х2 | 0х3 | 0х4 | . | 0хC0000000 | . | 0х200000000 |
0x00 | char a; | 0хFF | char b; | . | 0xFF | . | 0x31 |
Давайте для удобства превратим ряд в таблицу, стеллаж.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x0000 | a | ||||||||||||||
0x0010 | b | ||||||||||||||
0x0020 | |||||||||||||||
0x0030 | c | ||||||||||||||
0x0040 | |||||||||||||||
0x0050 | |||||||||||||||
0x0060 |
char a; // переменная находится по адресу 0х0004, не проинициализирована
char b; // переменная находится по адресу 0х0018, не проинициализирована
int c=344; // переменная находится по адресу 0х0030, проинициализирована значением 344
Объявляя переменную, мы по сути выделяем ей память определенного размера(1,4,8 байт). Размер зависит от типа данных.
После объявления переменной, в этой занятой ячейке памяти ничего не находится, точнее, там мусор от прошлых использований.
При инициализации переменной, в эту занятую ячейку памяти заносится заданное программой значение.
Процессору без разницы как вы назовете переменную. Он все равно оперирует адресами.
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | a | b | c | d | e | f |
0x0000 | a | ||||||||||||||
0x0010 | b | ||||||||||||||
0x0020 | |||||||||||||||
0x0030 | c=344 | ||||||||||||||
0x0040 | *d | ||||||||||||||
0x0050 | *e=&c | ||||||||||||||
0x0060 |
Объявляя переменную-указатель, мы по сути выделяем ей память всегда одинакового размера, необходимого для адресации любой ячейки памяти (4 байта для 32битных систем, 8 байт для 64 битных). Размер зависит от разрядности системы и задается при компиляции программы.
Чтобы узнать адрес любой переменной, используется символ & перед именем.
Чтобы узнать что лежит по адресу в указателе, нужно «разыменовать» его, используя символ * (звездочка).
Если еще непонятно, предлагаю перейти к примерам.
Вот небольшой пример указателей на языке Си++. Желательно самому все проверить(DevCpp)
Кое-что интересно есть в примере.
*(e+1) и e[1] дают идентичный результат.
Есть еще кое-что интересно в примере на C/C++.
a2->a при работе напрямую с указателем на структуру используется стрелка «->»
Указатели на массив.
И на структуру.
Особенности использования в C++.
В массиве можно обратиться к элементам через указатель 2 способами:
В структуре можно обратиться к членам через указатель 2 способами:
Все это справедливо для языка программирования Си++
Указатели в C++: адрес и удаление
Всем привет! В этом уроке мы разберём то, что очень тесно связано с памятью в компьютере. С помощью этого можно улучшать работу своей программы. Как вы догадались с названия урока, это — указатели.
Адрес переменной в C++
Давайте приведем аналогию с круизным лайнером. В нем, как и в отеле, имеются номера. Вот, например, при покупке номера вам могут дать номер — 0x155 (да, мы понимаем, что не в одном лайнере или отеле не станут записывать номера в шестнадцатеричном виде, но давайте все таки немного отвлечемся). А друг может оказаться в номере 0x212 — так и с переменными, они могут получить разный путь. И только сам компьютер при создании переменной знает, где она находится.
Переменные, которые вы создаете в программе, по её завершению автоматически удаляются, чтобы не нагружать операционную память вашего компьютера.
Пример удаления переменных
В играх присутствует хорошая графика, различные спецэффекты. Например, тот же дым. Все это — переменная (может не одна!), которой в будущем придётся уничтожиться навсегда. А вот, если бы она не удалилась, то она бы своей фоновой работой понемножку нагружала бы наш компьютер.
Поэтому в C/C++ присутствует возможность обратиться к переменной, и, если требует ситуация, удалить и создать её вовсе в другом участке программы, когда это, конечно, нам будет нужно.
Что такое указатели в C++
Указатели — это с самого начала переменные, уже в которых хранится адрес других переменных.
Чтобы пользоваться указателями, вам нужно использовать два оператора:
Как создать указатели в C++
Давайте посмотрим, какую конструкцию нужно использовать, чтобы создать указатели:
Указатели
Указатели
Э то, пожалуй, самая сложная и самая важная тема во всём курсе. Без понимания указателей дальнейшее изучении си будет бессмысленным. Указатели – очень простая концепция, очень логичная, но требующая внимания к деталям.
Определение
У казатель – это переменная, которая хранит адрес области памяти. Указатель, как и переменная, имеет тип. Синтаксис объявления указателей
Например
float *a;
long long *b;
Два основных оператора для работы с указателями – это оператор & взятия адреса, и оператор * разыменования. Рассмотрим простой пример.
Рассмотрим код внимательно, ещё раз
Была объявлена переменная с именем A. Она располагается по какому-то адресу в памяти. По этому адресу хранится значение 100.
Создали указатель типа int.
Теперь переменная p хранит адрес переменной A. Используя оператор * мы получаем доступ до содержимого переменной A.
Чтобы изменить содержимое, пишем
После этого значение A также изменено, так как она указывает на ту же область памяти. Ничего сложного.
Теперь другой важный пример
Будет выведено
4
4
8
4
Несмотря на то, что переменные имеют разный тип и размер, указатели на них имеют один размер. Действительно, если указатели хранят адреса, то они должны быть целочисленного типа. Так и есть, указатель сам по себе хранится в переменной типа size_t (а также ptrdiff_t), это тип, который ведёт себя как целочисленный, однако его размер зависит от разрядности системы. В большинстве случаев разницы между ними нет. Зачем тогда указателю нужен тип?
Арифметика указателей
В о-первых, указателю нужен тип для того, чтобы корректно работала операция разыменования (получения содержимого по адресу). Если указатель хранит адрес переменной, необходимо знать, сколько байт нужно взять, начиная от этого адреса, чтобы получить всю переменную.
Во-вторых, указатели поддерживают арифметические операции. Для их выполнения необходимо знать размер.
операция + N сдвигает указатель вперёд на N*sizeof(тип) байт.
Например, если указатель int *p; хранит адрес CC02, то после p += 10; он будет хранить адрес СС02 + sizeof(int)*10 = CC02 + 28 = CC2A (Все операции выполняются в шестнадцатиричном формате). Пусть мы создали указатель на начало массива. После этого мы можем «двигаться» по этому массиву, получая доступ до отдельных элементов.
Заметьте, каким образом мы получили адрес первого элемента массива
Массив, по сути, сам является указателем, поэтому не нужно использовать оператор &. Мы можем переписать пример по-другому
Если же указатели равны, то они указывают на одну и ту же область памяти.
Указатель на указатель
У казатель хранит адрес области памяти. Можно создать указатель на указатель, тогда он будет хранить адрес указателя и сможет обращаться к его содержимому. Указатель на указатель определяется как
Очевидно, ничто не мешает создать и указатель на указатель на указатель, и указатель на указатель на указатель на указатель и так далее. Это нам понадобится при работе с двумерными и многомерными массивами. А вот простой пример, как можно работать с указателем на указатель.
Указатели и приведение типов
Т ак как указатель хранит адрес, можно кастовать его до другого типа. Это может понадобиться, например, если мы хотим взять часть переменной, или если мы знаем, что переменная хранит нужный нам тип.
В этом примере мы пользуемся тем, что размер типа int равен 4 байта, а char 1 байт. За счёт этого, получив адрес первого байта, можно пройти по остальным байтам числа и вывести их содержимое.
У казатель до инициализации хранит мусор, как и любая другая переменная. Но в то же время, этот «мусор» вполне может оказаться валидным адресом. Пусть, к примеру, у нас есть указатель. Каким образом узнать, инициализирован он или нет? В общем случае никак. Для решения этой проблемы был введён макрос NULL библиотеки stdlib.
Принято при определении указателя, если он не инициализируется конкретным значением, делать его равным NULL.
По стандарту гарантировано, что в этом случае указатель равен NULL, и равен нулю, и может быть использован как булево значение false. Хотя в зависимости от реализации NULL может и не быть равным 0 (в смысле, не равен нулю в побитовом представлении, как например, int или float).
Это значит, что в данном случае
вполне корректная операция, а в случае
поведение не определено. То есть указатель можно сравнивать с нулём, или с NULL, но нельзя NULL сравнивать с переменной целого типа или типа с плавающей точкой.
Примеры
Теперь несколько примеров работы с указателями
1. Пройдём по массиву и найдём все чётные элементы.
2. Когда мы сортируем элементы часто приходится их перемещать. Если объект занимает много места, то операция обмена местами двух элементов будет дорогостоящей. Вместо этого можно создать массив указателей на исходные элементы и отсортировать его. Так как размер указателей меньше, чем размер элементов целевого массива, то и сортировка будет происходить быстрее. Кроме того, массив не будет изменён, часто это важно.
3. Более интересный пример. Так как размер типа char всегда равен 1 байт, то с его помощью можно реализовать операцию swap – обмена местами содержимого двух переменных.
В этом примере можно поменять тип переменных a и b на double или любой другой (с соответствующим изменением вывода и вызова sizeof), всё равно мы будет обменивать местами байты двух переменных.
4. Найдём длину строки, введённой пользователем, используя указатель
C Урок 26. Указатели и адреса. Часть 1
На данном уроке мы рассмотрим интересную тему. Это указатели и адреса.
На данной теме очень много начинающих программистов впадают в ступор, особенно когда видят звёздочки и амерсанды, которые для этого используются в языке C.
Вернее, мы, конечно же, данную тему не рассмотрим. Мы её только легонько коснёмся. Рассмотреть в рамках одного урока такую серьёзную тему не получится. По ней будет как минимум 4 урока. Мы лишь проведём сегодня первое знакомство. Но это будет самое главное. Так как понимание указателей и адресов начинается именно здесь.
Надеюсь, если вы попали сюда, то вы решили освоить программирование на языке, что говорит о том, что с информатикой вы уже знакомы и преподавать её здесь я, конечно же, не буду ибо это огромная наука, что вы уже знаете, как организована архитектура компьютера, что такое память физическая, виртуальная, адресная шина, магистраль, процессор и т.д.
Напомню то, что память, предоставляемая программе или процессу операционной системой, имеет адресацию совершенно не ту, какую этот предоставленный участок занимает физически. То есть адреса совершенно другие и не факт, что кажущийся непрерывным участок памяти в виртуальном адресном пространстве будет также непрерывен в физическом. Он может быть фрагментирован. Но это нам лишь облегчает работу, так как нам не нужно озадачиваться тем, каким образом организована память физически. Поэтому, если я буду рассказывать здесь о памяти, то буду иметь в виду именно виртуальную, имеющую те адреса, которые мы видим в среде программирования в отладке.
Также, думаю, практически все в курсе, что у меня есть некоторое количество уроков по приобретающим всё большую популярность микроконтроллерам. Вот там как раз мы работаем напрямую с памятью физической и в отладке видим именно её. Но это не важно. Урок данный будет вполне актуален и для МК. Нам же абсолютно нет дела до того, какую память мы видим. А видим мы именно ту память, с которой мы работаем.
Организована память, предоставленная программе, примерно вот таким образом
У каждой ячейки памяти существует адрес.
Почему адреса следуют друг за другом не подряд, а пропускаются по 4 байта?
Можно конечно показать и подряд, но я показал именно с учётом того, что мы пишем приложение под 32-разрядную систему. Хотя у нас практически у большинства установлены операционные системы 64-разрядные, но приложения, написанные под 32-битные системы, там прекрасно работают. У нас даже компилятор mingw предназначен для 32-разрядных систем. Пока мы будем писать с расчётом именно на 32-разрядные системы, так как, во-первых, они легче для понимания, во-вторых, из соображений совместимости, так как 32-рязрядные системы пока ещё существуют и хочется, чтобы наша программа запускалась и прекрасно работала и на них. Также у нас получается то, что наш урок актуален и для 32-разрядных МК, например для тех же stm32.
Так вот к чему я это всё?
А к тому, что считаю, что удобнее работать также с 32-битными ячейками памяти и зачастую к ним идёт приравнивание.
Вообще в одной такой ячейке получается по 4 ячейки 8-битных. Например, если мы объявляем переменную типа char и присваиваем ей какое-то значение, то это значение займёт только такую ячейку.
Ну что ж. Представим, что мы объявили переменную типа int, которая скорей всего занимает в памяти 32 бита (но не факт, это иногда проверять надо), назвав её, например a. Затем присвоили ей какое-то значение. В данном случае нам операционная система выделит какую-то ячейку памяти, ну пусть, например вот эту
Теперь у нашей переменной появился адрес, так как операционная система предоставила ей определённую ячейку памяти. Операционная система не знает имя этой переменной, оно ей не нужно, даже и процессор при выполнении программы не знает имени переменной, он работает с её значением и обращается к ней именно по адресу. А как же мы можем узнать адрес этой переменной?
Мы конечно же зададимся вопросом. А зачем нам адрес переменной? Мы же знаем её имя и в процессе написания программы мы прекрасно можем обратиться к нашей переменной по имени. Скажу лишь, что есть такие ситуации, когда нам требуется именно адрес переменной, так как, например, если мы передали значение переменной в другую функцию в качестве параметра, то у нас в данной функции создастся копия этой переменной и мы будем работать с ней, поэтому, если мы вдруг решим изменить значение нашей переменной, то мы не сможем этого сделать, имя её в другой функции не видно, то есть переменная не попала в область видимости. Но если мы как-то передадим адрес, то мы сможем уже работать с реальной переменной. И это лишь одна ситуация, таких очень много.
Чтобы нам как-то запомнить адрес нашей переменной, то есть тот, который в нашем случае 0x0061FF14, то мы можем создать для нашей переменной указатель.
Для этого мы можем заранее объявить переменную-указатель типа int. Указатель такого типа будет указывать на переменные именно такого типа.
Объявляется переменная-указатель какого-либо типа данных вот таким образом
Объявление указателя очень похоже на объявление обычной переменной, только тут появляется наша пресловутая звёздочка, которая почему-то всех путает. А почему именно, да потому что ставится она возле имени переменной, а после типа данных ставится пробел. Можно, конечно, поступить и наоборот, поставить эту звёздочку возле типа данных, потом поставить пробел и лишь потом имя. Всё работать будет точно так же и это было бы правильнее для понимания, так как звёздочка относится именно к типу данных. Именно тип данных вместе со звёздочкой и есть тип переменной-указателя. Но существует соглашение, согласно которому звёздочка ставится перед именем. Поэтому мы просто должны чётко понимать, что тип данных вместе со звёздочкой – это то, что мы объявили переменную-указатель, а её имя – это только имя указателя. Потом ещё будет причина непонимания смысла указателей, когда мы встретим звёздочку немного в другом случае. Но это чуть позже.
Как это всё будет выглядеть практически?
Сначала давайте объявим нашу обычную переменную a и присвоим ей какое-нибудь значение. Пусть она будет даже беззнаковая
unsigned int a;
a = 0xFE340056;
А теперь мы объявим переменную-указатель такого же типа
unsigned int *p_a;
Имя нашей переменной p_a. Я специально дал такое имя, чтобы показать то, что указатель будет у нас хранить адрес переменной a. Пока мы этот указатель только объявили, мы ему не присваивали никаких адресов. Это обычная переменная, которой также выделена ячейка памяти, но она пока не хранит никакой информации, пока там какое-то случайное значение.
Вообщем, в памяти у нас пока примерно вот такая картина
То есть, у нас есть переменная типа указателя на unsigned int, которая пока не хранит практически ничего, а переменная a имеет уже значение, хранящееся по определённому адресу.
Как же присвоить адрес нашей переменной a нашему указателю p_a?
А делается это вот таким вот образом.
Существует специальная операция, называемая операцией взятия адреса.
Оператор данной операции выглядит в виде амперсанда
Данная операция является унарной, используется в выражениях и оператор амперсанд ставится перед переменной, адрес которой мы хотим получить
Здесь тоже присутствует некая путаница. Кто-то путает данный оператор с точно таким же оператором побитового И, ровно как звёздочку изредка расценивают как операцию умножения.
Давайте теперь присвоим адрес нашей переменной a переменной-указателю p_a
p_a = &a;
Здесь мы больше никаких звёздочек не используем. Звёздочка была нужна только при объявлении переменной-указателя. Теперь мы с ней работаем, как с обычной переменной и присваиваем ей адрес другой переменной, используя операцию взятия адреса.
Вот теперь переменная p_a хранит в себе адрес переменной a
Получается, что p_a знает теперь адрес a. То есть если, как в жизни, у нас есть такой человек, который знает адрес другого человека.
И теперь у нас наступила такая ситуация. Если нам потребовался адрес другого человека, то как мы его можем узнать у того человека, который этот адрес знает.
Ну конечно же любой ответит: Да как! Спросить у него надо!
Ну это в жизни. А здесь как? Да вообще элементарно. У нас p_a его и хранит, он является его значением. Просто присваиваем значение этого p_a любым переменным.
Тогда будет другой вопрос. А можем ли мы узнать у p_a не адрес, а значение нашей переменной a, адрес которой он хранит? Ну это типа спросить у человека, знающего адрес человека: А не знаешь ли ты, сколько лет другому человеку?
Ответ: мы это также можем сделать. Для этого мы должны произвести операцию разыменования указателя p_a.
Для разыменования у нас существует вот такой оператор
Да автор просто над нами прикалывается! – скажете вы.
Нет, не прикалывается. Опять эта звёздочка!
Но здесь она уже служит унарным оператором разыменования указателя, который используется вот таким образом
Используется также в выражениях, но не только. Использование такой конструкции возможно перед оператором присваивания. Но об этом чуть позже.
Как же отличить звёздочку, обозначающую указатель, от звёздочки, являющейся оператором разыменования указателя? А очень просто. Во втором случае перед звёздочкой не будет типа данных.
Поэтому, чтобы получить значение нашей переменной a, используя её указатель, мы можем сделать, например вот так
unsigned int b = *p_a;
Теперь переменная b получит значение переменной a, которое мы взяли у указателя на эту переменную, применив операцию разыменования.
С помощью разыменования мы можем также и изменить значение переменной a, используя указатель p_a, который хранит её адрес
*p_a = 0xE2222222;
Может также возникнет вопрос. Если указатель – это также переменная, которая тоже имеет в памяти свой адрес. Она же находится в памяти и занимает ячейку. А можем ли мы создать и на неё указатель?
Ну конечно же можем!
Указатель на указатель мы можем объявить с помощью двух звёздочек, вот таким вот образом
unsigned int **p_p_a;
Наш новый указатель теперь тоже получит своё место в памяти
Только этот указатель пока ни на что не показывает, то есть он пока не хранит никаких адресов.
И теперь мы вот таким образом присваиваем адрес указателя p_a, который указывает на переменную a, нашему новому указателю
p_p_a = &p_a;
Получится теперь у нас вот такая картина в памяти
То есть теперь p_p_a хранит адрес p_a, который в свою очередь хранит адрес a.
Вообщем какой-то человек знает адрес другого человека, который в свою очередь знает адрес третьего человека.
Ну и тогда резонный вопрос: а можем ли мы у этого человека узнать возраст третьего человека, ведь он знает адрес другого человека, который знает адрес третьего человека и который может спросить у третьего человека его возраст и сказать первому человеку?
Ну конечно можем. И причём в нашем случае программирования на языке C всё гораздо проще.
Мы также используем две звёздочки в операции разыменования
unsigned int b = **p_p_a;
У нас получилось как бы двойное разыменование. Мы разыменовали адрес указателя p_a, получив его значение, являющееся адресом a и второй звёздочкой разыменовали и этот адрес, получив значение уже самой переменной a.
Вот так. Теперь, надеюсь, к вам немного пришло понимание физики адресов. У адресов существует также ещё и арифметика, но об этом в другом уроке.
Теперь давайте немного поговорим о массивах.
Например существует у нас массив данных типа unsigned char, мы его объявили и сразу проинициализировали
Данный массив также занял в памяти какое-то место и у каждого элемента массива теперь есть свои адреса в памяти
Теперь нам удобнее смотреть в памяти все адреса, а не каждый четвёртый, так как у нас массив такого типа. Причём мы можем получить указатель на каждый его элемент, но нам это не нужно пока.
А как же получить указатель на массив, ну, то есть на то место с которого он начинается, на первый его элемент, вернее, если быть точным, на нулевой?
А, в принципе, его получать-то особо не всегда надо, так как имя массива и есть указатель на массив. Вот такая интересная получается история.
Но если нам вдруг нужно назначить указатель на массив другой переменной, то мы этот также можем прекрасно сделать, например, вот так.
unsigned char *p_uch = &uch[0];
Мы в данном случае получаем адрес его нулевого элемента и присваиваем его переменной-указателю такого же типа, как и наш массив.
Теперь переменная p_uch хранит в себе адрес массива, то есть является указателем на массив uch
Здесь конечно же возникнуть может ещё один резонный вопрос. Почему указатель на наш массив занял в памяти 4 ячейки, а, следовательно, и 4 байта?
А потому, что указатель хоть и является указателем на тип unsigned char, но сама переменная-указатель всегда будет 32-битной, так как по-другому и не получится. Каким образом мы в 8 битах можем хранить число 0x0061FF11? Да никаким, вот поэтому и 4 ячейки.
Причём, наш указатель на массив, который мы только что создали, мы также можем использовать как имя нашего же массива. Если мы, например обратимся с помощью выражения p_uch[5], то мы получим имя 5 элемента массива uch. Это конечно уже тема не данного урока, а больше арифметика указателей, но тем не менее мы можем это делать.
В следующей части нашего урока мы отработаем все изученные в данной части урока приёмы с указателями и адресами на практике.
Смотреть ВИДЕОУРОК (нажмите на картинку)