Держите использование указателей простым и понятным
Причина
Сложные манипуляции с указателями — основной источник ошибок.
Примечание
Используйте gsl::span вместо этого. Указатели должны ссылаться только на одиночные объекты. Арифметика указателей хрупка и легко ошибиться, это источник многих, очень многих серьёзных ошибок и нарушений безопасности. span — это безопасный тип с проверкой границ для доступа к массивам данных. Доступ к массиву с известными границами с использованием константного индекса может быть проверен компилятором.
Пример (плохой)
void f(int* p, int count)
{
if (count < 2) return;
int* q = p + 1; // ПЛОХО
ptrdiff_t d;
int n;
d = (p - &n); // OK
d = (q - p); // OK
int n = *p++; // ПЛОХО
if (count < 6) return;
p[4] = 1; // ПЛОХО
p[count - 1] = 2; // ПЛОХО
use(&p[0], 3); // ПЛОХО
}
Пример (хороший)
void f(span<int> a) // ЛУЧШЕ: используйте span в объявлении функции
{
if (a.size() < 2) return;
int n = a[0]; // OK
span<int> q = a.subspan(1); // OK
if (a.size() < 6) return;
a[4] = 1; // OK
a[a.size() - 1] = 2; // OK
use(a.data(), 3); // OK
}
Примечание
Индексирование с переменным значением сложно проверить на безопасность как инструментами, так и людьми. span — это безопасный тип с проверкой границ во время выполнения для доступа к массивам данных. at() — ещё одна альтернатива, обеспечивающая проверку границ при одиночных обращениях. Если нужны итераторы для доступа к массиву, используйте итераторы из span, построенного над массивом.
Пример (плохой)
void f(array<int, 10> a, int pos)
{
a[pos / 2] = 1; // ПЛОХО
a[pos - 1] = 2; // ПЛОХО
a[-1] = 3; // ПЛОХО (но легко обнаруживается инструментами) -- замены нет, просто не делайте этого
a[10] = 4; // ПЛОХО (но легко обнаруживается инструментами) -- замены нет, просто не делайте этого
}
Пример (хороший)
Используйте span:
void f1(span<int, 10> a, int pos) // A1: измените тип параметра на span
{
a[pos / 2] = 1; // OK
a[pos - 1] = 2; // OK
}
void f2(array<int, 10> arr, int pos) // A2: добавьте локальный span и используйте его
{
span<int> a = {arr.data(), pos};
a[pos / 2] = 1; // OK
a[pos - 1] = 2; // OK
}
Используйте at():
void f3(array<int, 10> a, int pos) // АЛЬТЕРНАТИВА B: используйте at() для доступа
{
at(a, pos / 2) = 1; // OK
at(a, pos - 1) = 2; // OK
}
Пример (плохой)
void f()
{
int arr[COUNT];
for (int i = 0; i < COUNT; ++i)
arr[i] = i; // ПЛОХО, нельзя использовать непостоянный индекс
}
Пример (хороший)
Используйте span:
void f1()
{
int arr[COUNT];
span<int> av = arr;
for (int i = 0; i < COUNT; ++i)
av[i] = i;
}
Используйте span и range-for:
void f1a()
{
int arr[COUNT];
span<int, COUNT> av = arr;
int i = 0;
for (auto& e : av)
e = i++;
}
Используйте at() для доступа:
void f2()
{
int arr[COUNT];
for (int i = 0; i < COUNT; ++i)
at(arr, i) = i;
}
Используйте range-for:
void f3()
{
int arr[COUNT];
int i = 0;
for (auto& e : arr)
e = i++;
}
Примечание
Инструменты могут предложить переписать обращения к массиву с динамическими индексами, используя at() вместо этого:
static int a[10];
void f(int i, int j)
{
a[i + j] = 12; // ПЛОХО, может быть переписано как...
at(a, i + j) = 12; // OK -- проверка границ
}
Пример
Преобразование массива в указатель (что язык делает практически всегда) устраняет возможности для проверки, поэтому избегайте этого:
void g(int* p);
void f()
{
int a[5];
g(a); // ПЛОХО: пытаемся ли мы передать массив?
g(&a[0]); // OK: передаём один объект
}
Если хотите передать массив, скажите об этом явно:
void g(int* p, size_t length); // старый (опасный) код
void g1(span<int> av); // ЛУЧШЕ: измените g().
void f2()
{
int a[5];
span<int> av = a;
g(av.data(), av.size()); // OK, если нет другого выбора
g1(a); // OK -- нет затухания, используется неявный конструктор span
}
Контроль
- Отмечайте любую арифметическую операцию над выражением типа указатель, результатом которой является значение типа указатель.
- Отмечайте любое индексирующее выражение над выражением или переменной типа массив (статический массив или
std::array), где индексатор не является константным выражением времени компиляции со значением от0до верхней границы массива. - Отмечайте любое выражение, которое опирается на неявное преобразование типа массив в тип указатель.
Это правило является частью профиля безопасности границ.