C Урок 27. Адресная арифметика. Часть 2



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

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

Начнём вот с такой интересной операции

 

 

Здесь у нас и звёздочка, здесь у нас и инкрементирование. Давайте всё же разберёмся, что у нас тут происходит.

Операция инкрементирования подразделяется на префисксное инкрементирование (преинкремент) и постфиксное инкрементирование (постинкремент).

Данные операции обе являются унарными и отличаются по синтаксису тем, в каком месте стоит оператор по отношению к операнду — сзади или спереди.

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

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

 

 

Только дело тут не совсем в приоритетах. А дело вот в чём. Значение переменной, к которой применяется постинкремент или постдекремент, при использовании в каких-либо выражениях (например при присваивании с инкрементом и т.д.) изменяется после того, как оно будет использовано в выражении, а в случае использования преинкремента и предекремента — до использования.

Звучит не совсем понятно, но как раз наши операции заодно и помогут нам это понять.

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

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

Выведем наши значения в консоль

 

 

Проверим, как у нас сработала наша комплексная операция

 

 

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

Посмотрим, как выглядит наша гибридная операция в отладке

 

 

Здесь сначала увеличивается на 4 значение 4-байтного (DWORD) типа, находящееся в стеке, это и есть наш указатель, а затем уже берётся значение этого адреса и берётся значение, находящееся по этому уже увеличенному адресу.

Идём дальше.

Закомментируем вот этот участок

 

 

Попробуем вот такую операцию

 

 

Мы поменяли инкремент со звёздочкой (оператором разыменования).

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

Выведем наши значения

 

 

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

 

 

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

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

 

 

Здесь сначала берётся значение по адресу, хранящемуся в стеке. Это значение адреса, который указывает на 5 элемент массива. Затем по этому адресу берётся значение, затем оно увеличивается на 1, а затем обратно укладывается в стек.

Снова закомментируем предыдущий код.

Теперь попробуем вот такую операцию

 

 

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

Выведем результаты в консоль

 

 

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

 

 

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

Посмотрим это в отладке. Здесь даже дизассемблирование не требуется.

 

 

Когда код выполнится до этого места (когда присвоение значения переменной n уже произойдёт)

 

 

то у нас наши переменные примут вот такие значения

 

 

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

Ещё одна операция

 

 

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

Давайте выведем значения на консоль

 

 

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

 

 

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

Посмотрим, как это выглядит в отладке, если остановить код вот в этом месте

 

 

Вот такие у нас будут значения наших переменных

 

 

Надеюсь, всё здесь наглядно и понятно.

Теперь напоследок давайте поиграем ещё с нашим массивом, верней не с нашим, а создадим ещё один из 4-х подобных элементов, закомментировав предыдущий код

 

 

Затея у нас будет такая. Мы попробуем подобраться с помощью указателей к отдельным байтам элементов нашего массива.

 

 

Сначала выведем значение элементов нашего массива и их адресов в консоль

 

 

Посмотрим, как это выглядит

 

 

Создадим указатели на нулевой и второй элементы нашего массива

 

 

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

 

 

Вот так это будет выглядеть

 

Попробуем повыводить различные элементы массива с помощью наших новых указателей

 

 

Посмотрим, как работатет данный код

 

 

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

Теперь интересный возникает вопрос. Элемент массива, получается, — это значение, взятое по адресу указателя со смещением номера элемента. Следовательно, индекс массива — это само смещение. Но ведь мы же можем декрементировать указатели, а также можем вычитать константы. Тогда можем ли мы в качестве индекса элемента использовать отрицательное число? Конечно же можем! Давайте попробуем

 

 

Посмотрим результат работы нашего кода

 

 

Всё отлично работает. Если мы возьмем элемент номер -1 массива p2_b, то он даст нам первый элемент оригинального массива. Если мы, конечно, возьмём элемент -1 массива p0_b, который несёт в себе адрес нулевого элемента оригинального массива, уменьшенный, на 1, то мы, конечно же получим значение, находящееся не в границах нашего массива. Вот такие, вообщем, операции.

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

 

 

Посмотрим это значение

 

 

Мы видим, что это ни что иное, как значение нулевого элемента массива. Так и должно быть.

А теперь вот такая арифметика.

Мы попробуем не через оператор квадратные скобки (представим себе, что у нас их нет вообще), а через арифметику указателя, а именно сложение указателя с константой, вывести значение какого-нибудь другого элемента массива, например, второго, для этого мы передадим вот такое выражение нашей функции printf

 

 

Проверим, как это работает

 

А работает всё прекрасно, мы получили значение 2-го элемента массива посредством смещения указателя на него, которым является его имя.

А вот теперь начнём реализовывать нашу затею.

Сначала мы попробуем получить адрес самого первого байта нашего массива (а не элемента)

 

 

Мы передали функции printf значение указателя на первый байт, используя преобразование типа указателя к unsigned char *. Затем мы опять преобразовали тип, чтобы получить значение адреса, оно же у нас всегда 32-битное.

А вот и результат

 

 

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

Ну а теперь самое главное. Мы получим значение этого байта

 

 

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

Попробуем теперь вот такой код

 

 

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

 

 

А вывелись в консоль у нас самые младшие байты наших обычных 4-байтовых элементов.

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

А надо делать вот так

 

 

Вот теперь другое дело. Мы прибавляем константу к уже приведённому к 8-битному типу значению указателя, а уж затем применяем разыменование.

Вот теперь будет всё нормально

 

 

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

Ну раз так всё хорошо, то давайте выведем все 16 байтов раздельно всего нашего 4-элементного массива, предварительно выведя в консоль адрес этого массива, а уж затем байты с их адресами (вернее не с полными адресами, а только с их младшими байтами, чтобы было компактнее)

 

 

Мы по маске очищаем все старшие байты адреса и выводим только младший байт. Здесь, как видим, амперсанд уже используется как побитовый оператор И.

Проверим, как работает наш код

 

 

Все наши байты вывелись.

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

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

 

 

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

 

Исходный код

 

 

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

 

C Адресная арифметика

2 комментария на “C Урок 27. Адресная арифметика. Часть 2
  1. Тема интересная )) В концовке урока много скобок нагородили )) Оно конечно не мешает, но вот так тоже работает:

    printf(«Value *(unsigned char*)b is %02X\n», *(unsigned char*)b);
    printf(«Value *(unsigned char*)(b+1) is %02X\n», *((unsigned char*)b+1));
    printf(«Value *(unsigned char*)(b+2) is %02X\n», *((unsigned char*)b+2));

  2. Тема интересная )) В концовке урока много скобок нагородили )) Оно конечно не мешает, но вот так тоже работает:

    printf(«Value *(unsigned char*)b is %02X\n», *(unsigned char*)b);
    printf(«Value *(unsigned char*)(b+1) is %02X\n», *((unsigned char*)b+1));
    printf(«Value *(unsigned char*)(b+2) is %02X\n», *((unsigned char*)b+2));

    ну и так далее..

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

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

*