Перейти к содержимому

Арифметика указателей

  • Что делает сложение указателя и числа
  • Что дает отнимание двух указателей
  • UB в случае, если указатели не из того же блока
  • stack
int arr[2]{};
int* p = arr;
p = p + 1;
*p = 2;
std::cout << arr[1];
std::cout << std::endl;
Ответ

p + 1 перемещает указатель на 1 базовый элемент, то есть на 1 int, то есть на 4.

p в конце функции равен &arr[1]. Считывание из *p эквивалетно считыванию из arr[1].

int arr[2]{};
int* p = arr;
*(p + 1) = 6;
std::cout << *p << std::endl;
std::cout << arr[1] << std::endl;
Ответ

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

Еще важно заметить, что *(p + 1) не изменяет p, а создает временный измененный адрес. *p считает 0 из первой ячейки массива.

int arr[2]{};
int* pStart = &arr[0];
int* p = pStart + 1;
ptrdiff_t x = p - pStart;
std::cout << x;
std::cout << std::endl;
ptrdiff_t x1 = pStart - p;
std::cout << x1;
std::cout << std::endl;
Ответ

Оператор - между указателями дает количество базовых элементов между ними. Результат может быть и отрицательным.

Выведется 1, а потом -1.

4. (продвинутый уровень): Отнимание адресов

Заголовок раздела «4. (продвинутый уровень): Отнимание адресов»
#include <cstdint>
#include <iostream>
int main()
{
int arr[2]{};
int* a = &arr[0];
int* b = &arr[1];
uintptr_t a1 = reinterpret_cast<uintptr_t>(a);
uintptr_t b1 = reinterpret_cast<uintptr_t>(b);
ptrdiff_t x = static_cast<ptrdiff_t>(b1 - a1);
std::cout << x;
std::cout << std::endl;
}
Ответ

uintptr_t это число, занимающее столько же байтов, сколько и int* (вероятно, 8 байтов).

b1 - a1 применяется между адресами как числами. Это выражение уже не имеет информацию о том, какого типа указатели были вначале. Оно работает на числовых значениях указателя.

Ответ будет 4 — разница указателей в байтах.

5. (продвинутый уровень): Выход за переделы переменной

Заголовок раздела «5. (продвинутый уровень): Выход за переделы переменной»
int a = 5;
int b = 6;
int* pa = &a;
int* pb = pa + 1;
std::cout << *pb;
std::cout << std::endl;
Ответ

Технически, это считается UB (undefined behavior). Но концептуально, выведется 6 из переменной b.

6. (продвинутый уровень): Выход за пределы массива

Заголовок раздела «6. (продвинутый уровень): Выход за пределы массива»
int a[2]{};
int b = 6;
int* p = &a[2];
*p = 7;
std::cout << b;
std::cout << std::endl;
Ответ

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

Здесь выведется 7.

int arr1[2]{ 1, 2 };
int arr2[2]{ 3, 4 };
arr1 = arr2;
Ответ

Несмотря на то, что логически это должно скопировать каждый элемент из arr2 в arr1, программа не скомпилируется. Такой синтаксис просто не работает в C++.

int arr1[2]{ 1, 2 };
int arr2[2]{ 3, 4 };
*arr1 = *arr2;
Ответ

В *arr1 = *arr2, имена массивов взяты как выражения. В этом случае, они воспринимаются как указатели на первый элемент. Соответственно, это перезапишет arr1[0] на arr2[0], то есть 3.