JS: Массивы

Массивы в памяти компьютера

Работая на таких высокоуровневых языках, как JavaScript, позволительно не знать устройство массивов для решения повседневных задач. С другой стороны, подобное понимание делает код менее магическим и даёт возможность заглядывать чуть дальше.

Массивы в Cи

Реальные массивы лучше всего рассматривать на языке , который, с одной стороны, достаточно простой и понятный, с другой — очень близок к железу и не скрывает от нас практически ничего. Когда мы говорим про примитивные типы данных, такие как "строка" или "число", то на интуитивном уровне всё довольно понятно: под каждое значение выделяется некоторый размер памяти (в соответствии с типом), в которой и хранится само значение. А как должна выделиться память под хранение массива? И что такое массив в памяти? На уровне хранения понятия массив не существует. Массив представляется цельным куском памяти, размер которого вычисляется по следующей формуле: количество элементов * количество памяти под каждый элемент. Из этого утверждения есть два интересных вывода:

  • Размер массива — фиксированная величина. Те динамические массивы (изменяющие свой размер во время работы), с которыми мы имеем дело во многих языках, реализованы уже внутри языка, а не на уровне железа.
  • Все элементы массива имеют один тип и занимают одно и то же количество памяти. Благодаря этому появляется возможность простым умножением (по формуле, описанной выше) получить адрес той ячейки, в которой лежит нужный нам элемент. Именно это происходит под капотом, при обращении к элементу массива под определённым индексом.

Массив в Си

Фактически, индекс в массиве — смещение относительно начала куска памяти, содержащего данные массива. Адрес, по которому расположен элемент под конкретным индексом, рассчитывается так: индекс * количество памяти, занимаемое одним элементом (для данного типа данных на данной архитектуре). Пример на Си:

// Инициализация массива из пяти элементов типа int
// Предположим, что int занимает 2 байта
// Общее количество памяти выделенное под массив int * 5 = 2 * 5 = 10 байт
int numbers[] = {19, 10, 8, 17, 9};
numbers[3]; // 17

Если предположить, что тип int занимает в памяти 2 байта, то адрес элемента, соответствующего индексу 3, вычисляется так: начальный адрес + 3 * 2. Начальный адрес — это адрес ячейки памяти, начиная с которой располагается массив. Он формируется во время выделения памяти под массив. Ниже пример расчёта адресов памяти под разные элементы массива numbers:

// Первый элемент
// Начальный адрес + 2 * 0 = начальный адрес
numbers[0]; // 19

// Начальный адрес + 2 * 1 = начальный адрес + 2
// То есть сместились на 2 байта
numbers[1]; // 10

// Начальный адрес + 2 * 2 = начальный адрес + 4
// То есть сместились на 4 байта
numbers[2]; // 8

// Последний элемент
// Начальный адрес + 2 * 4 = начальный адрес + 8
// То есть сместились на 8 байт
// И сам элемент занимает 2 байта. В сумме как раз 10
numbers[4]; // 9

Теперь должно быть понятно, почему индексы в массиве начинаются с нуля. 0 — означает отсутствие смещения.

Но не все данные имеют одинаковый размер. Как будет храниться массив строк? Строки ведь имеют разную длину, а значит требуют разное количество памяти для своего хранения. Один из способов сохранить строки в массиве на языке Си – создать массив массивов (тут нужно понимать, что любая строка в Си это массив символов). Вложенные массивы обязательно должны быть одного размера, невозможно обойти физические ограничения массивов. Хитрость в том, что этот размер должен быть достаточно большой, чтобы туда поместились необходимые строки.

// Массив из трех элементов, внутри которого массивы по 10 элементов
// Это значит, что здесь можно хранить 3 строки длиной не больше 10 символов
char strings[3][10] = {
   "spike",
   "tom",
   "jerry"
};

strings[0]; // spike

Безопасность

В отличие от высокоуровневых языков, в которых код защищён от выхода за границу массива, в таком языке, как Си, выход за границу не приводит к ошибкам (на самом деле он может приводить к segfault, но это здесь не важно). Обращение к элементу, индекс которого находится за пределами массива, вернёт данные, которые лежат в той самой области памяти, куда его попросили обратиться (в соответствии с формулой выше). Чем они окажутся — никому не известно (но они будут проинтерпретированы в соответствии с типом массива. Если массив имеет тип int, то вернётся число). Выход за границу массива активно эксплуатируется хакерами для взлома программ.

Массивы в динамических языках

В динамических языках, таких как JavaScript, устройство массивов значительно сложнее чем в си. Так как типы данных вычисляются автоматически во время выполнения кода. Массив в такой среде не может работать как в си. Неизвестно, данные каких типов окажутся внутри в процессе работы.

Массивы в таких языках содержат не сами данные, а ссылки (адреса в памяти) на них. Тогда становится не важно, что хранить. Любое значение в массиве – адрес, имеющий одинаковый размер независимо от данных, на которые он указывает. Такой подход делает массивы гибкими, но с другой стороны, более медленными.

Кроме того, массивы в динамических языках тоже динамические. То есть их размер может увеличиваться или уменьшаться в процессе работы программы. Технически это работает так: если ссылки (помним, что данные там не хранятся) в массив не помещаются, то интерпретатор внутри себя создает новый массив большего размера (обычно в два раза) и переносит все ссылки туда. Динамические массивы очень упрощают процесс разработки, но за это тоже приходится платить скоростью.


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Для полного доступа к курсу нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».