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

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

Массивы в си

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

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

Массив в Си

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

Пример на Си:

// Инициализация массива из пяти элементов типа int
// В этом месте резервируется память под него
// Непрерывный кусок памяти размером _количество элементов * количество байт под int_
int mark[] = {19, 10, 8, 17, 9};
// _Начальный адрес + 3 * количество байт под int_
// Так рассчитывается фактический адрес, по которому располагаются данные
mark[3]; // 17

Если предположить, что тип int занимает в памяти 2 байта (зависит от архитектуры), то адрес элемента, соответствующего индексу 3, вычисляется так: начальный адрес + 3 * 2. Для индекса 1начальный адрес + 1 * 2.

В такой формуле расчета адреса, есть ровно один способ физически разместить данные в начале доступной памяти – использовать нулевой индекс: начальный адрес + 0 * размер элемента конкретного типа = начальный адрес.

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

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

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

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

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

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

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

strings[0]; // spike

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

Получить доступ
120
курсов
900
упражнения
2000+
часов теории
3200
тестов

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

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

  • 120 курсов, 2000+ часов теории
  • 900 практических заданий в браузере
  • 360 000 студентов

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

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

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

Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы PHP-разработчик
Профессия

PHP-разработчик

Разработка веб-приложений на Laravel
23 июня 8 месяцев

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

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

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