Арифметика указателей
Концепты
Заголовок раздела «Концепты»- Что делает сложение указателя и числа
- Что дает отнимание двух указателей
- UB в случае, если указатели не из того же блока
- stack
Примеры на понимание
Заголовок раздела «Примеры на понимание»1. Переход адреса на 1
Заголовок раздела «1. Переход адреса на 1»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].
2. Переход по адресу из выражения
Заголовок раздела «2. Переход по адресу из выражения»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 из первой ячейки массива.
3. Отнимание указателей
Заголовок раздела «3. Отнимание указателей»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.
7. Присваивание массива (1)
Заголовок раздела «7. Присваивание массива (1)»int arr1[2]{ 1, 2 };int arr2[2]{ 3, 4 };arr1 = arr2;Ответ
Несмотря на то, что логически это должно скопировать каждый элемент
из arr2 в arr1, программа не скомпилируется.
Такой синтаксис просто не работает в C++.
8. Присваивание массива (2)
Заголовок раздела «8. Присваивание массива (2)»int arr1[2]{ 1, 2 };int arr2[2]{ 3, 4 };*arr1 = *arr2;Ответ
В *arr1 = *arr2, имена массивов взяты как выражения.
В этом случае, они воспринимаются как указатели на первый элемент.
Соответственно, это перезапишет arr1[0] на arr2[0], то есть 3.