Как найти нужный указатель

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

Для чего нужен поиск указателей

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

Найти одноуровневый указатель

Запускаем Cheat Engine. Находим и копируем в нижнее окно адрес переменной, которая отвечает за нужный параметр. Правым кликом по адресной строке вызываем меню, находим строку «Find out what writes to this address». Ставим break на запись и разрешаем запуск отладчика. Идем в игру и тратим часть золота или теряем одну жизнь — чтобы изменить показатель. Возвращаемся к Cheat Engine и видим в окне отладчика новые строки. Выбираем одну типа mov и переходим во вкладку «More information». Правым кликом открываем меню и выбираем «Copy info to clipboard» — скопированное переносим в блокнот, закрываем отладчик.

Найти одноуровневый указатель в Cheat Engine

Далее переходим в главное окно программы и в поисковой строке вводим адрес из указанной области 07AF.., отмечаем галочкой НЕХ и тип значения 4Б, — запускаем поиск. В результатах поиска ищем постоянный адрес – выделяется зеленым. Копируем в нижнее окно и кликаем дважды по строке «Adress».

Как найти указатель в Чит Энджин

Копируем адрес сверху, отмечаем галочкой «Pointer» и вставляем в нижнее выпавшее поле. Тип определяем исходный. Далее при помощи вендового калькулятора рассчитываем смещение между первоначальным адресом, копированным в блокнот и найденным зеленым. Результат вставляем во второе поле снизу и жмем «Ок». После этого правым кликом по значению – «Value» выбираем в меню «Show as decimal» — отражать показатели в десятичном формате. Итог сохраняем в типе файла *.СТ. При загрузке этого файла в Cheat Engine с запуском уровня не надо будет снова искать переменные.

Найти одноуровневый указатель в Чит Энджин

Найти многоуровневый указатель

Многоуровневый – это такой, который ссылается не на искомую информацию, а на другой указатель. Таких уровней может найтись сколько угодно. Многоуровневая адресация усложняет процесс поиска цепочки указателей. Обработка занимает время. Сканирование памяти проводится 8-12 раз с перезапуском игры до тех пор, пока не выявится постоянный результат и один показатель не отразит хоть раз одинаковый результат с игровым параметром при перезагрузке.

Cheat Engine (помогите разобраться!)

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

пример:
1. в инглише. вместо Exact value ставишь Unknown initial value (4 байта, скорей всего)
2. скан
3. побегал, например выносливость уменьшилась, ставишь паузу, так как значение не должно равняться или быть больше того, которое искали в первый раз, тут думаю понятно почему, выставляешь Decreased value (уменшилось)
4. скан
5. отдохнул, выносливость увеличилась, ставишь Increaced value (увеличилось)
6. скан
7. повторить процедуру с пункта 3 до нахождения нужного значения
таким образом можно найти практически любые значения не отображаемые в числовом виде

трейнер скачай, проще будет. или таблицу для Cheat Engine поищи, может есть. по мне так табличка самое оно

[СТАТЬЯ] Находим указатели в играх

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

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

Я возьму для примера tutorial cheat engine step 8.Везде принцип один и тот же, то-есть таким же образом вы сможете найти указатели во многих играх, в том числе и в Perfect world, Jade Dinasty.

Во первых если у вас нету программы которая работает с памятью, нужно скачать программу Cheat engine.Ее можно скачать с офицального сайта — http://www.cheatengine.org/
Переходим по ссылке, нажимаем download cheat engine, качаем, устанавливаем.

Открываем Tutorial-i386.exe в папке с программой.
Мы видим такое окно:
В поле password вводим пароль 8 ступени — 525927 и жмем ок.

Открываем Cheat Engine нажимаем на светящийся монитор и выбираем процесс Tutorial-i386.exe

Теперь настройка закончена.Перейдем к взлому.

Смотрим на окошко туториала —
Там есть две кнопки — change value и change pointer.Из этого уже известно что там будет хотя бы один указатель.И есть значение, в данном случае оно у меня 1621.

Переходим в окно Cheat Engine и в строку value вводим 1621.Ничего не меняем.Жмем first scan.Если оно одно — хорошо.Если несколько, жмем change value в строку вводим следующее значение и жмем next scan.

Жмем на значение два раза и оно появляется внизу.Перейдем к находке указателей.
Жмем внизу по значение правой клавишей мыши и жмем find out what writes to this adress.

Появится новое окно.Оно спросит разрешение, нажмите yes.
Перейдите в туториал и нажмите change value.В том окне появится функция.Жмем по ней и more info.Зеленым выделено смещение.Его нужно запомнить, оно понадобится нам позже.Желтым указан указатель в hex’e.Это первый указатель, а их здесь 4.

Переходим в окно Cheat Engine и жмем new scan.Ставим галку напротив hex и вводим адресс.

Внизу жамкаем по нему find out what acces to this adress.

Находим так указатели пока не дойдем до зеленого указателя, он статический, последний указатель.

Теперь закрываем лишние вкладки cheat engine переходим в главное окно и жмакаем add adress manually.Жмем галку напротив pointer и 3 раза add pointer.Зеленый указатель вставляем в самую нижнюю строчку.
Помните я говорил вам запомнить смещение?Теперь оно нам нужно.Ставим его в поля оффсет в порядке 18 0 14 c и сверху должен быть адресс самого первого значения.Внимание на рисунок.

Жмем ОК и у нас внизу появилось еще одно значение.Замораживаем его — ставим крестик в окошке и изменяем значение на 5000.Переходим в окно туториала жмем change pointer — и вуаля!Туториал пройден.

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

  1. В поле поиска на панели задач выполните поиск по запросу мышьи выберите ее в списке.

  2. В разделе параметров мыши выберите Дополнительные параметры мыши в ссылках в правой части страницы.

  3. В разделе Свойства мыши на вкладке Параметры указателя в нижней части выберите Обозначить расположение указателя при нажатии CTRL и нажмите ОК.

  4. Чтобы отобразить движение указателя, нажмите клавишу CTRL.

Нужна дополнительная помощь?

Нужны дополнительные параметры?

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

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

Кратко об указателях в Си: присваивание, разыменование и перемещение по массивам

Время на прочтение
6 мин

Количество просмотров 49K

Приветствую вас, дорогие читатели. В данной статье кратко описаны основные сведения об указателях в языке Си. Кроме основных операций с указателями (объявление, взятие адреса, разыменование) рассмотрены вопросы безопасности типов при работе с ними. К сожалению, в данной статье вы не найдёте информацию по операциям сравнений указателей. Однако, статья будет полезна новичкам, а также тем, кто работает с массивами. Все примеры в данной статье компилировались компилятором gcc (восьмой версии).

Введение

Указатель – переменная, которая хранит адрес сущностей (т.е. других переменных любого типа, будь то структура, или массив), и над которой возможно выполнять операцию разыменования (dereferencing). Адрес обычно выражен целым положительным числом. Диапазон адресов зависит от архитектуры компьютера. Указателю надо указать тип переменной, адрес которой он хранит, или же использовать ключевое слово void, для обозначения указателя, хранящего адрес чего-угодно (т.е. разрешён любой тип). Указатели объявляются как и обычные переменные, с той разницей, что имя типа переменной указателя имеет префикс, состоящий как минимум из одной звёздочки (*). Например:

int a = 12; /* usual variable */
int * ptr = &a; /* ptr-variable which contains address of variable a */
int **pptr = &ptr; /* ptr-variable which contains address of variable ptr */
int aval = **pptr; /* get value by adress which is contained in pptr. */
int aval2 = *ptr; /* get value of a by address (value of ptr) */

Количество звёздочек лишь указывает на длину цепочек хранимых адресов. Поскольку указатель также является переменной и имеет адрес, то его адрес также можно хранить в другом указателе. В выше приведённом примере адрес переменной a сохраняется в переменной-указателе ptr. Адрес же самой переменной ptr сохраняется в другом указателе pptr. Чтобы получить адрес переменной, перед её именем надо поставить знак амперсанда (&). Наконец, чтобы выполнить обратную операцию, т.е. получить значение (содержимое) по адресу, хранимому в указателе, имя указателя предваряется звёздочкой, почти как при объявлении. Почти, потому что одной звёздочки достаточно чтобы “распаковать” указатель. Поскольку pptr указывает по адресу на значение, хранимое в ptr, то необходимо два раза применить операцию разыменования.

Указатели в предыдущем примере хранят адрес переменной определённого типа. В случае, когда применяются указатели типа void (любого типа), то прежде чем распаковать значение по адресу, необходимо выполнить приведение к типизированному указателю. Следующий пример является версией предыдущего, но с использованием указателя любого типа.

int b = 0xff;
void *pb = &b;
void **ppb = &pb;
int bval1 = *((int *) pb);
int bval2 = *((int *) *ppb);

В данном примере адреса хранятся в указателе типа void. Перед получением значения по адресу, хранимым в pb, необходимо привести указатель pb к типу int*. Затем, воспользоваться стандартной операцией разыменования. Что касается указателя ppb, то он разыменовывается два раза. Первый раз до приведения к типу, для получения содержимого переменной pb, на которую он указывает. Второй раз – после приведения к типу int*.

Изменения значения переменной через указатель.

Так как указатель хранит адрес переменной, мы можем через адрес не только получить значение самой переменной, но также его изменить. Например:

char a = 'x';
char *pa = &a; /* save address of a into pa */
*pa = 'y'; /* change content of variable a */
printf("%cn", a); /* prints: y */

Как было сказано выше, указатели хранят адреса. Естественно, что адреса могут указывать не только на ячейки данных переменных в вашей программе, но и на другие вещи: адрес стека процедур, адрес начала сегмента кода, адрес какой-то процедуры ядра ОС, адрес в куче и т. д. Логично, что не все адреса можно использовать напрямую в программе, поскольку некоторые из них указывают на те участки памяти, которые нельзя изменять (доступ для чтения), или которые нельзя затирать. В случае, при обращении к участку, доступному только для чтения, при попытке изменить значение получим ошибку Segmentation Fault (SF).

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

И ещё, указатели могут указывать на один и тот же объект. Например:

int a = 123;

int *p1 = &a;

//Теперь p2 хранит тот же адрес, что и p1.
int *p2 = &a; 

*p1 -= 3; // a = 123 - 3.
printf("*p2 = %dn", *p2); //Выведет 120

Этот простой пример показывает, что через адреса можно менять содержимое простых переменных, а также остальных указателей, ссылающихся на тоже самое. Таким образом, указатель p2 как бы является псевдонимом (alias) для p1.

Передача параметров через указатели.

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

int swap(int *a, int *b){
	if(a == NULL || b == NULL)
  	return -1;
	int temp = *a;
  *a = *b;
  *b = temp;
  return 0;
}

Здесь переменные а и b меняются своими значениями друг с другом (при условии, что параметры содержат не нулевой адрес). Отметим ещё раз, что мы можем изменить содержимое, указываемое по параметру-указателю методов. И, конечно, мы можем стереть данный адрес, присвоив параметру новое значение.

Проверка типов и массивы

Как было сказано, указатели хранят адреса переменных. Несмотря на указание типа для переменной указателя, это не мешает присвоить ему адрес переменной другого типа, если вы компилируете БЕЗ флагов. Например, следующий код не скомпилируется, если вы включили флаги -Werror -Wall.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv){
	int *ptr = NULL;
	float a = 23.2;
	ptr = &a;
	printf("%.1fn", *ptr);
	return 0;
}

Конечно, компилятор gcc и без -Wall заметит недопустимую операцию в 7 строке кода. Флаг -Wall покажет все предупреждения компилятора. Главный флаг -Werror не позволит компилировать код, если есть предупреждения.

Что же касается массивов, то для массива не нужно предварять имя переменной амперсандом, поскольку компилятор автоматически при присваивании адреса массива присвоит адрес первого его элемента в указатель. Для многомерных массивов потребуются указатели на массивы, а не массивы указателей. Первые имеют форму объявления вида int (*arr)[], а вторые вида int *arr[]. В квадратных скобках обязательно нужно указать размер массива. Для трёхмерных массивов потребуется уже две пары скобок, например int (*arr)[2][2]. Для четырёхмерных – три и так далее.

// В ПУСТОМ теле метода main.

int A[2] = {40, 20};

// A -> (int *) ptr to A[0] element, &A -> (int (*)[]) -> ptr to whole Array.
int *ptr = A;
printf("ptr -> A[1] = %dn", *(ptr + 1)); // A[1] => 20.

//Illegal usage of A.
// int a_2 = ++A;  //expected lvalue.

//But with ptr you can do this.
int b_2 = *++ptr; //Now ptr contains address of A[1]. (b_2 = A[1]);

int (*ptr2)[2] = &A; //ptr to array, not to literal element.

//*ptr2 => get array.
//**ptr2 => get first element of array.
//*ptr2 + 1 => get address of second element of array.
printf("ptr2 -> A[1] = %dn", *( *ptr2 + 1) ); 

int M[2][2] = { {1, 2} , {3, 4} };

// (*mp)[k] => (*mp)[k] => mp[0][k].
int (*mp)[2] = M; //again you must not add '&' to variable M.
printf("M[0][0] = %dn", **mp);//get array and extract it first element
printf("M[1][0] = %dn", **(mp + 1));//move to the address of second element
printf("M[1][1] = %dn", *( *(mp + 1) + 1));

В выше приведённом коде даны примеры для работы с массивами (одномерными и двумерными). В квадратных скобках указывается размер последнего измерения. Важно помнить, что первое разыменование приводит вас ко всему массиву (т. е. к типу int *). А второе разыменование распаковывает элемент данного массива. В случае одномерного массива, у нас всего одна ячейка, и указатель ссылается на неё. В случае двумерного массива, у нас две ячейки – массивы, а указатель ссылается на первую. Для перемещения на второй массив, достаточно прибавить единицу к адресу, хранимому в переменной mp, например, так mp + 1 . Чтобы получить первый элемент второго массива, надо два раза распаковать указатель с соответствующим адресом массива, т.е. **(mp + 1).

Постоянные (const) и указатели.

Напомним, чтобы сделать переменную с постоянным, фиксированным значением, надо добавить ключевое слово const перед её именем (до имени типа или после). Например:

const int i1 = 10;
int const i2 = 222;

// Warning: variable e3 is unitialized. With -Werror it won't be compiled.
// (Внимание: переменной e3 не присвоено значение. С флагом gcc -Werror 
//  данный код не скомпилируется).
// const int e3;

Для объявления указателя на постоянное значение, ключевое слово const должно быть ПЕРЕД звёздочкой.

int A[2] = {100, 200};
const int *a0 = A;
printf("content of a0 = %dn", *a0);

//*a0 *= 10; //error: cannot change constant value.

a0 = (A + 1); // A[1]
printf("content of a0 = %dn", *a0); //prints: A[1]

В примере выше была создана переменная-указатель, ссылающееся на постоянное значение. Слово const перед звёздочкой указывает, что нельзя менять содержимое напрямую (путём разыменования, обращения к ячейке). Но сама переменная указатель постоянной не является. А значит, ей можно присвоить новый адрес. Например, адрес следующей ячейки в массиве.

Чтобы запретить менять адрес (значение переменной) указателя, надо добавить слово const ПОСЛЕ звёздочки. Кроме того, можно добавить ключевые слова const перед и после '*', чтобы сделать переменную фиксированной ещё сильнее, например так:

// Переменная с постоянным адресом и постоянным содержимым.
const int *const ptr = A; // constant address with constant content

// Переменная с постоянным адресом (содержимое можно менять)
int *const ptr2 = A; // constant address only.

// Переменная с постоянным содержимым, но с изменяемым адресом (значение справа)
const int *ptr3 = A; // constant content only (can change address (rvalue))

Указатели

Что такое указатели

Последнее обновление: 03.01.2023

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

Определение указателя

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

тип_данных* название_указателя;

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

Например, определим указатель на объект типа int:

int *p;

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

int main(void)
{
    int x = 10;		// определяем переменную
    int *p;			// определяем указатель
    p = &x;			// указатель получает адрес переменной
    return 0;
}

Получение адреса данных

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

Что важно, переменная x имеет тип int, и указатель, который указывает на ее адрес тоже имеет тип int. То есть должно быть соответствие по типу.

Какой именно адрес имеет переменная x? Для вывода значения указателя можно использовать специальный спецификатор %p:

#include <stdio.h>

int main(void)
{
	int x = 10;
	int *p;
	p = &x;
	printf("%p n", p);		// 0060FEA8
	return 0;
}

В моем случае машинный адрес переменной x – 0x0060FEA8. (Для адресов в памяти применяется шестнадцатеричная система.) Но в каждом отдельном случае адрес может быть иным. Фактически адрес представляет целочисленное значение, выраженное в шестнадцатеричном формате.

То есть в памяти компьютера есть адрес 0x0060FEA8, по которому располагается переменная x.

Указатели в языке Си

Так как переменная x представляет тип int,
то на большинстве архитектур она будет занимать следующие 4 байта (на конкретных архитектурах размер памяти для типа int может отличаться). Таким образом,
переменная типа int последовательно займет ячейки памяти с адресами 0x0060FEA8, 0x0060FEA9, 0x0060FEAA, 0x0060FEAB.

Указатели в Си

И указатель p будет ссылаться на адрес, по которому располагается переменная x, то есть на адрес 0x0060FEA8.

Стоит отметить, что при выводе адреса указателя функция printf() ожидает, что указатель будет представлять void*, то есть указатель на значение типа
void. Поэтому некоторые компиляторы при некоторых настройках могут при компиляции отображать предупреждения. И чтобы было все канонически правильно, то
переданный указатель нужно преобразовать в указатель типа void *:

printf("%p n", (void *)p);

Получение значения по адресу

Но так как указатель хранит адрес, то мы можем по этому адресу получить хранящееся там значение, то есть значение переменной x. Для этого применяется
операция * или операция разыменования (dereference operator). Результатом этой
операции всегда является объект, на который указывает указатель. Применим данную операцию и получим значение переменной x:

#include <stdio.h>

int main(void)
{
	int x = 10;
	int *p;
	p = &x;
	printf("Address = %p n", (void*) p);
	printf("x = %d n", *p);
	return 0;
}

Консольный вывод:

Address = 0060FEA8
x = 10

Используя полученное значение в результате операции разыменования мы можем присвоить его другой переменной:

int x = 10;
int *p  = &x;
int y = *p;		// присваиваем переменной y значение по адресу из указателя p
printf("x = %d n", y);	// 10

Здесь присваиваем переменной y значение по адресу из указателя p, то есть значение переменной x.

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

int x = 10;
int *p = &x;
*p = 45;
printf("x = %d n", x);	 // 45

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

Создадим еще несколько указателей:

#include <stdio.h>

int main(void)
{
	char c = 'N';
	int d = 10;
	short s = 2;
	
	char *pc = &c;			// получаем адрес переменной с типа char
	int *pd = &d;			// получаем адрес переменной d типа int
	short *ps = &s;			// получаем адрес переменной s типа short
	
	printf("Variable c: address=%p t value=%c n", (void*) pc, *pc);
	printf("Variable d: address=%p t value=%d n", (void*) pd, *pd);
	printf("Variable s: address=%p t value=%hd n", (void*) ps, *ps);
	return 0;
}

В моем случае я получу следующий консольный вывод:

Variable c: address=0060FEA3	value=N
Variable d: address=0060FE9C	value=10
Variable s: address=0060FE9A	value=2

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

Работа с памятью в языке Си

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

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

//объявление указателя
/*тип данных*/  * /*имя указателя*/;

Принцип объявления указателей такой же, как и принцип объявления переменных. Отличие заключается только в том, что перед именем ставится символ звёздочки *. Визуально указатели отличаются от переменных только одним символом. При объявлении указателей компилятор выделяет несколько байт памяти, в зависимости от типа данных отводимых для хранения некоторой информации в памяти. Чтобы получить значение, записанное в некоторой области, на которое ссылается указатель нужно воспользоваться операцией разыменования указателя *. Необходимо поставить звёздочку перед именем и получим доступ к значению указателя. Разработаем программу, которая будет использовать указатели.

  • MVS
  • Code::Blocks
  • Dev-C++
  • QtCreator
// pointer1.cpp: определяет точку входа для консольного приложения.

#include "stdafx.h"
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int var = 123; // инициализация переменной var числом 123
    int *ptrvar = &var; // указатель на переменную var (присвоили адрес переменной указателю)
    cout << "&var    = " << &var << endl;// адрес переменной var содержащийся в памяти, извлечённый операцией взятия адреса 
    cout << "ptrvar  = " << ptrvar << endl;// адрес переменной var, является значением указателя ptrvar 
    cout << "var     = " << var << endl; // значение в переменной var
    cout << "*ptrvar = " << *ptrvar << endl; // вывод значения содержащегося в переменной var через указатель, операцией разименования указателя
    system("pause");
    return 0;
}
// pointer1.cpp: определяет точку входа для консольного приложения.

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int var = 123; // инициализация переменной var числом 123
    int *ptrvar = &var; // указатель на переменную var (присвоили адрес переменной указателю)
    cout << "&var    = " << &var << endl;// адрес переменной var содержащийся в памяти, извлечённый операцией взятия адреса
    cout << "ptrvar  = " << ptrvar << endl;// адрес переменной var, является значением указателя ptrvar
    cout << "var     = " << var << endl; // значение в переменной var
    cout << "*ptrvar = " << *ptrvar << endl; // вывод значения содержащегося в переменной var через указатель, операцией разименования указателя
    return 0;
}

В строке 10 объявлен и инициализирован адресом переменной var указатель ptrvar. Можно было сначала просто объявить указатель, а потом его инициализировать, тогда были бы две строки:

int *ptrvar;        // объявление указателя
     ptrvar = &var; // инициализация указателя

В программировании принято добавлять к имени указателя приставку ptr, таким образом, получится осмысленное имя указателя, и уже с обычной переменной такой указатель не спутаешь. Результат работы программы (см. Рисунок 1).

CppStudio.com

&var    = 0x22ff08
ptrvar  = 0x22ff08
var     = 123
*ptrvar = 123
Для продолжения нажмите любую клавишу . . .

Рисунок 1 — Указатели в С++

Итак, программа показала, что строки 11 и 12 выводят идентичный адрес, то есть адрес переменной var, который содержится в указателе ptrvar. Тогда как операция разыменования указателя *ptrvar обеспечивает доступ к значению, на которое ссылается указатель.

Указатели можно сравнивать не только на равенство или неравенство, ведь адреса могут быть меньше или больше относительно друг друга. Разработаем программу, которая будет сравнивать адреса указателей.

  • MVS
  • Code::Blocks
  • Dev-C++
  • QtCreator
// pointer.cpp: определяет точку входа для консольного приложения.

#include "stdafx.h"
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int var1 = 123; // инициализация переменной var1 числом 123
    int var2 = 99; // инициализация переменной var2 числом 99
    int *ptrvar1 = &var1; // указатель на переменную var1
    int *ptrvar2 = &var2; // указатель на переменную var2
    cout << "var1    = " << var1 << endl;
    cout << "var2    = " << var2 << endl;
    cout << "ptrvar1 = " << ptrvar1 << endl;
    cout << "ptrvar2 = " << ptrvar2 << endl;
    if (ptrvar1 > ptrvar2) // сравниваем значения указателей, то есть адреса переменных
        cout << "ptrvar1 > ptrvar2" << endl;
    if (*ptrvar1 > *ptrvar2) // сравниваем значения переменных, на которые ссылаются указатели
        cout << "*ptrvar1 > *ptrvar2" << endl;
    system("pause");
    return 0;
}
// pointer.cpp: определяет точку входа для консольного приложения.

#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
    int var1 = 123; // инициализация переменной var1 числом 123
    int var2 = 99; // инициализация переменной var2 числом 99
    int *ptrvar1 = &var1; // указатель на переменную var1
    int *ptrvar2 = &var2; // указатель на переменную var2
    cout << "var1    = " << var1 << endl;
    cout << "var2    = " << var2 << endl;
    cout << "ptrvar1 = " << ptrvar1 << endl;
    cout << "ptrvar2 = " << ptrvar2 << endl;
    if (ptrvar1 > ptrvar2) // сравниваем значения указателей, то есть адреса переменных
        cout << "ptrvar1 > ptrvar2" << endl;
    if (*ptrvar1 > *ptrvar2) // сравниваем значения переменных, на которые ссылаются указатели
        cout << "*ptrvar1 > *ptrvar2" << endl;
    return 0;
}

Результат работы программы показан на рисунке 2.

CppStudio.com

var1    = 123
var2    = 99
ptrvar1 = 0x22ff04
ptrvar2 = 0x22ff00
ptrvar1 > ptrvar2
*ptrvar1 > *ptrvar2
Для продолжения нажмите любую клавишу . . .

Рисунок 2 — Указатели в С++

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

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

Указатели на указатели

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

  • MVS
  • Code::Blocks
  • Dev-C++
  • QtCreator
// pointer.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h"
#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
    int var = 123; // инициализация переменной var числом 123
    int *ptrvar = &var; // указатель на переменную var
    int **ptr_ptrvar = &ptrvar; // указатель на указатель на переменную var
    int ***ptr_ptr_ptrvar = &ptr_ptrvar;
    cout << " vartt= " << var << endl;
    cout << " *ptrvart= " << *ptrvar << endl;
    cout << " **ptr_ptrvar   = " << **ptr_ptrvar << endl; // два раза разименовываем указатель, так как он второго порядка 
    cout << " ***ptr_ptrvar  = " << ***ptr_ptr_ptrvar << endl; // указатель третьего порядка
    cout << "n ***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar ->      var -> "<< var << endl;
    cout << "t  " << &ptr_ptr_ptrvar<< " -> " << "    " << &ptr_ptrvar << " ->" << &ptrvar << " -> " << &var << " -> " << var << endl;
    system("pause");
    return 0;
}
// pointer.cpp: определяет точку входа для консольного приложения.

#include <iostream>
using namespace std;

int main()
{
    int var = 123; // инициализация переменной var числом 123
    int *ptrvar = &var; // указатель на переменную var
    int **ptr_ptrvar = &ptrvar; // указатель на указатель на переменную var
    int ***ptr_ptr_ptrvar = &ptr_ptrvar;
    cout << " vartt= " << var << endl;
    cout << " *ptrvart= " << *ptrvar << endl;
    cout << " **ptr_ptrvar   = " << **ptr_ptrvar << endl; // два раза разименовываем указатель, так как он второго порядка
    cout << " ***ptr_ptrvar  = " << ***ptr_ptr_ptrvar << endl; // указатель третьего порядка
    cout << "n ***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar ->      var -> "<< var << endl;
    cout << "t  " << &ptr_ptr_ptrvar<< " -> " << "    " << &ptr_ptrvar << " ->" << &ptrvar << " -> " << &var << " -> " << var << endl;
    return 0;
}

На рисунке 3 показан результат работы программы.

CppStudio.com

 var            = 123
 *ptrvar        = 123
 **ptr_ptrvar   = 123
 ***ptr_ptrvar  = 123

 ***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar ->      var -> 123
          0x22ff00 ->     0x22ff04 ->0x22ff08 -> 0x22ff0c -> 123
Для продолжения нажмите любую клавишу . . .

Рисунок 3 — Указатели в С++

Данная программа доказывает тот факт, что для получения значения количество разыменований указателя должно совпадать с его порядком. Логика n-кратного разыменования заключается в том, что программа последовательно перебирает адреса всех указателей вплоть до переменной, в которой содержится значение. В программе показана реализация указателя третьего порядка. И если, используя такой  указатель (третьего порядка) необходимо получить значение, на которое он ссылается, делается 4 шага:

  1. по значению указателя третьего порядка получить адрес указателя второго порядка;
  2. по значению указателя второго порядка получить адрес указателя первого порядка;
  3. по значению указателя первого порядка получить адрес переменной;
  4. по адресу переменной получить доступ к её значению.

Данные четыре действия показаны на рисунке 3 (две предпоследние строки). Верхняя строка показывает имена указателей, а нижняя строка их адреса.

На рисунке 4 показана схема разыменовывания указателя третьего порядка из верхней программы. Суть в том, что указатели связаны друг с другом через свои адреса. Причём, например, для указателя ptr_ptrvar данное число  0015FDB4 является  адресом, а для указателя ptr_ptr_ptrvar это же число является значением.

Указатели в С++

Рисунок 4 — Указатели в С++

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

Указатели на функции

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

// объявление указателя на функцию
/*тип данных*/ (* /*имя указателя*/)(/*список аргументов функции*/);

Тип данных определяем такой, который будет возвращать функция, на которую будет ссылаться указатель. Символ указателя и его имя берутся в круглые скобочки, чтобы показать, что это указатель, а не функция, возвращающая указатель на определённый тип данных.  После имени указателя идут круглые скобки, в этих скобках перечисляются все аргументы через запятую как в объявлении прототипа функции. Аргументы наследуются от той функции, на которую будет ссылаться указатель. Разработаем программу, которая использует указатель на функцию. Программа должна находить НОД – наибольший общий делитель. НОД – это наибольшее целое число, на которое без остатка делятся два числа, введенных пользователем. Входные числа также должны быть целыми.

  • MVS
  • Code::Blocks
  • Dev-C++
  • QtCreator
// pointer_onfunc.cpp: определяет точку входа для консольного приложения.

#include "stdafx.h"
#include <iostream>
using namespace std;
int nod(int, int ); // прототип указываемой функции
int main(int argc, char* argv[])
{
    int (*ptrnod)(int, int); // объявление указателя на функцию
    ptrnod=nod; // присваиваем адрес функции указателю ptrnod 
    int a, b;
    cout << "Enter first number: ";
    cin >> a;
    cout << "Enter second number: ";
    cin >> b;
    cout << "NOD = " << ptrnod(a, b) << endl; // обращаемся к функции через указатель
    system("pause");
    return 0;
}
int nod(int number1, int number2) // рекурсивная функция нахождения наибольшего общего делителя НОД
{
    if ( number2 == 0 ) //базовое решение
        return number1;
    return nod(number2, number1 % number2); // рекурсивное решение НОД
}
// pointer_onfunc.cpp: определяет точку входа для консольного приложения.

#include <iostream>
using namespace std;
int nod(int, int ); // прототип указываемой функции
int main(int argc, char* argv[])
{
    int (*ptrnod)(int, int); // объявление указателя на функцию
    ptrnod=nod; // присваиваем адрес функции указателю ptrnod
    int a, b;
    cout << "Enter first number: ";
    cin >> a;
    cout << "Enter second number: ";
    cin >> b;
    cout << "NOD = " << ptrnod(a, b) << endl; // обращаемся к функции через указатель
    return 0;
}
int nod(int number1, int number2) // рекурсивная функция нахождения наибольшего общего делителя НОД
{
    if ( number2 == 0 ) //базовое решение
        return number1;
    return nod(number2, number1 % number2); // рекурсивное решение НОД
}

Данная задача решена рекурсивно, чтоб уменьшить объём кода программы, по сравнению с итеративным решением этой же задачи. В строке 9 объявляется указатель,  которому в строке 10 присвоили адрес функции. Как мы уже говорили до этого, адресом функции является просто её имя. То есть данный указатель теперь указывает на функцию nod(). При объявлении указателя на функцию ни в коем случае не забываем о скобочках, в которые заключаются символ указателя и его имя. При объявлении указателя в аргументах указываем то же самое, что и в прототипе указываемой функции. Результат работы программы (см. Рисунок 5).

CppStudio.com

Enter first number: 16
Enter second number: 20
NOD = 4
Для продолжения нажмите любую клавишу . . .

Рисунок 5 — Указатели в С++

Вводим первое число, затем второе и программа выдает НОД. На рисунке 5 видно, что НОД для чисел 16 и 20 равен четырём.

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