Указатели

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

Последнее обновление: 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

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

Работа с памятью в языке Си
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850