Почему цикл Для Каждого продолжает обходить старый массив после создания нового?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8 часто встречаются ситуации, когда поведение встроенного языка кажется неочевидным. Одной из таких «загадок» является работа цикла Для Каждого при попытке переинициализировать коллекцию (массив, таблицу значений или список) непосредственно внутри тела цикла. Давайте подробно разберем, как устроены внутренние механизмы платформы и почему код ведет себя именно так.

Анализ исходной ситуации

Рассмотрим конкретный программный код, который вызывает у многих разработчиков удивление:


Массив = Новый Массив;
Массив.Добавить(1);
Массив.Добавить(2);
Массив.Добавить(3);
Массив.Добавить(4);

Для Каждого Эл из Массив Цикл
    Сообщить(Эл);
    Массив = Новый Массив;
    Массив.Добавить(6);
КонецЦикла;

На первый взгляд может показаться, что после первой итерации, когда мы присваиваем переменной Массив новый объект (Новый Массив), цикл должен либо прерваться, либо переключиться на новую коллекцию. Однако на экран будут выведены числа 1, 2, 3, 4. Выясним причину такого поведения, проанализировав работу итераторов.

Механизм внутреннего итератора

Разберем по шагам, что происходит в памяти компьютера, когда интерпретатор 1С встречает конструкцию Для Каждого:

  1. Фиксация ссылки: В момент входа в цикл платформа обращается к переменной, указанной после ключевого слова Из. В нашем случае это Массив.
  2. Создание скрытого объекта: Система получает не просто имя переменной, а прямую ссылку на адрес объекта в памяти. Для управления обходом создается внутренний скрытый итератор, который «привязывается» к этому конкретному адресу.
  3. Изоляция от переменной: С этого момента для работы цикла имя переменной Массив больше не имеет значения. Итератор уже «держит» исходный объект в памяти.

Проанализируем ситуацию с подменой: когда мы пишем Массив = Новый Массив, мы просто заставляем имя (указатель) Массив смотреть на другой участок памяти. Но старый участок памяти, где лежат числа 1, 2, 3 и 4, никуда не исчезает. Внутренний механизм цикла продолжает работать именно с ним, так как он сохранил ссылку на «старый» объект в момент начала выполнения конструкции Для Каждого.

Жизненный цикл объектов и сборщик мусора

Важно понимать, почему исходный массив не удаляется из памяти, хотя мы переназначили переменную. В 1С используется механизм подсчета ссылок. Пока на объект существует хотя бы одна ссылка, он живет. Внутренний итератор цикла — это и есть та самая «живая» ссылка, которая удерживает старый массив в памяти до тех пор, пока не будет выполнена команда КонецЦикла. Как только цикл завершится, скрытый итератор будет уничтожен, количество ссылок на старый массив станет равным нулю, и тогда сборщик мусора сможет освободить память.

Особенности различных коллекций

Подобное поведение характерно не только для массивов. Рассмотрим, как ведут себя другие типы данных:

Почему нельзя изменять состав коллекции внутри цикла

Хотя полная замена объекта (как в нашем примере) проходит «безопасно» (цикл просто дорабатывает со старыми данными), изменение состава того же самого объекта может привести к ошибкам или логическим сбоям. Рассмотрим подробнее:

  1. Удаление элементов: Если мы используем Для Каждого и удаляем текущий элемент из той же коллекции, итератор может «перескочить» через один элемент. Это происходит потому, что индексы оставшихся элементов смещаются, а итератор просто переходит к следующему порядковому номеру.
  2. Добавление элементов: В некоторых типах коллекций добавление элементов в процессе обхода может привести к бесконечному циклу, если итератор не фиксирует количество элементов в начале.

Именно поэтому стандарты разработки 1С рекомендуют использовать альтернативные способы обхода, если предполагается модификация коллекции.

Рекомендуемые способы решения задачи

Если нам необходимо изменять коллекцию или если нам важна предсказуемость при динамическом изменении данных, рассмотрим два надежных подхода.

Вариант 1: Обход по индексу (с конца)

Для удаления элементов лучше всего подходит цикл Для ... По, идущий в обратном порядке. Это гарантирует, что изменение индексов «хвоста» массива не повлияет на еще не обработанные элементы.


КоличествоЭлементов = Массив.Количество();
Для Сч = 1 По КоличествоЭлементов Цикл
    Индекс = КоличествоЭлементов - Сч;
    // Теперь мы можем безопасно удалять Массив[Индекс]
    Если Массив[Индекс] = НужноеУсловие Тогда
        Массив.Удалить(Индекс);
    КонецЕсли;
КонецЦикла;

Вариант 2: Использование вспомогательного массива

Если логика требует добавления или сложного изменения данных, проанализируем стратегию «отложенного действия». Мы сначала собираем объекты, которые нужно обработать или удалить, в отдельный массив, а затем выполняем действия над ними.


ЭлементыДляУдаления = Новый Массив;
Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
    Если СтрокаТЗ.Статус = "Устарело" Тогда
        ЭлементыДляУдаления.Добавить(СтрокаТЗ);
    КонецЕсли;
КонецЦикла;

Для Каждого Элемент Из ЭлементыДляУдаления Цикл
    ТаблицаЗначений.Удалить(Элемент);
КонецЦикла;

Резюме

Подводя итог, можно сказать, что результат 1, 2, 3, 4 в исходном тесте — это следствие четкой работы механизма ссылок платформы 1С. Для Каждого работает с объектом, а не с переменной.

Основные выводы для разработчика:

Таким образом, понимание работы ссылочной модели позволяет писать более стабильный и предсказуемый код, избегая «удивительных» результатов при отладке.

← На главную