Проблемы С-style массивов
#опытным
В наследство от языка С С++ достались статические массивы. Так называемые С-style массивы. Это проверенные средства языка, успешно решающие свои задачи. Но у них есть серьезные недостатки, которые в основном связаны с низкоуровневостью этого инструмента.
Давайте кратко повторим, что такое C-style массив.
Это непрерывная последовательность элементов одного типа и память под них выделяется на стеке(или реже в статический области, если массив глобальный) и автоматически освобождается при выходе из функции.
Определяется сишный массив вот так:
// создаем массив на 5 элементов,
// которые базово инициализируются мусором
int arr1[5];
// создаем массив на 5 элементов,
// которые инициализируются нулями
int arr1[5]{};
// создаем массив и предоставляем набор элементов, с помощью
// которых компилятор вычисляет длину массива и инициализирует элементы
int arr2[] = {1, 2, 3, 4, 5};
Размер памяти, занимаемый массивом равняется количеству его элементов помноженному на размер типа данных:
constexpr size_t array_size = 5;
int arr[array_size];
sizeof(arr) == array_size * sizeof(int); // true
Соответственно, для получения количества элементов массива, нужно поделить sizeof от массива на размер типа данных, которые он хранит.
auto array_size = sizeof(arr) / sizeof(Type);
В чем же его недостатки?
❗️ Массивы нельзя сравнивать напрямую, а только поэлементно. Напрямую сравниваются указатели на первый элемент.
int arr1[] = {0, 1, 2, 3};
int arr2[] = {0, 1, 2, 3};
// ложь так как сраниваются указатели,
// а они разные для разных объектов
arr1 == arr2;
❗️ Мимикрирование под массивы разрешает странную семантику с условиями и арифметическими операциями.
// создаем пустую строку в виде массива
char arr[] = "";
// условие будет всегда true, хотя мы создали пустую строку
if (arr);
// разрешается, но зачем? что значит прибавить к массиву число?
arr + 1;
❗️В С разрешены [массивы переменной длины](https://t.me/grokaemcpp/56) на уровне стандарта. И синтаксис у них ровно такой же, как и у статических массивов, только при его создании размер указывается не константой, а переменной. В С++ это не стандартная фича, а расширения компилятора. То есть нельзя писать кроссплатформенный код с использованием массивов переменной длины. Но за счет идентичного синтаксиса очень легко спутать один вид массива с другим и похерить переменосимость.
❗️ От синтаксиса сочетания функций и массивов хочется вырвать себе глаза, закрыть компьютер и уйти жить в лес:
int foo(int arr[4]);
// На самом деле такая сигнатура полностью эквивалентна int foo(int * arr),
// что позволяет принимать в функцию массив любой длины и указатели.
// В С++ нет синтаксиса приема массива по значению.
void foo(int (&arr)[4]); // зато есть синтаксис приема массива по ссылке
// Нормального синтаксиса для возврата массива из функции также не завезли.
// Вот воркэраунды.
int get_array()[10];
auto get_array() -> int[10];
❗️Массив не инкапсулирует в себе свой размер. Его нужно всегда вычислять, как мы говорили в начале.
❗️Из-за сложности синтаксиса, вы скорее всего захотите обрабатывать массивы с помощью функций с похожей сигнатурой:
void foo(int * p, size_t size);
Это потенциально может привести к доступу за границы выделенной области, так как функция foo ничего не знает про то, какой реальный размер имеет область памяти, на которую указывает p. Она должна доверять программисту и переданному значению size. А программисту верить - себя не уважать.
В общем, сишные массивы - это не объекты и не обладают преимуществами ООП и универсальной семантики для объектов в С++.
Поэтому стандартная библиотека предоставляет нам инструмент, который решает все проблемы C-style массивов. Это контейнер std::array. О нем мы поговорим в следующий раз.
Upgrade your tools. Stay cool.
#cppcore #goodoldc