СТАБУР
Технология· 9 мин чтения

ST программа падает на индексе массива: границы, циклы и данные с HMI

Почему ST падает на индексе массива: границы FOR, пустой массив, данные с HMI, онлайн-диагностика и отличие от NaN и ENUM.

Цикл FOR от 0 до 10, массив с индексами 0…9. На симуляторе программа крутится час. На объекте оператор на HMI выбирает «режим 12» из выпадающего списка, и ПЛК уходит в fault: array index out of bounds. Перезагрузка задачи, линия стоит. В логе CODESYS - исключение в строке с RecipeParams[ModeIndex], а ModeIndex пришёл с экрана без проверки. Другой частый случай: граница цикла FOR i := 1 TO ARRAY_LEN вместо TO ARRAY_LEN - 1 при нулевой индексации - падает на последней итерации, когда массив пустой после очистки рецепта.

Статья разбирает выход за границы массива в Structured Text: неверные границы FOR, пустой массив, значение с HMI вне диапазона, диагностика онлайн. Отдельно - отличие от NaN при делении и от сброса ENUM после загрузки: там программа не «падает» на индексе, хотя симптом на объекте похож. Не даём общий курс ST и не дублируем справочник синтаксиса - только сценарий индекса и практические проверки до пуска.

Короткий ответ

Падение ST на индексе массива - почти всегда индекс < 0 или индекс ≥ размер массива (или LENGTH/UPPER_BOUND). Проверьте границы FOR (0…N-1 при нулевой индексации), не используйте LENGTH как верхнюю границу без -1, валидируйте любой индекс с HMI до обращения к массиву. Пустой массив: LENGTH = 0, цикл FOR i := 0 TO 0 может выполниться один раз - защита IF LENGTH > 0 THEN. Онлайн: смотрите переменную индекса в момент fault, trace Force с экрана, сравните с ARRAY_UPPER_BOUND. От NaN и ENUM отличается: там fault на арифметике или логика идёт по неверной ветке без исключения индекса.

Как в IEC 61131-3 задаются границы массива

В CODESYS массив объявляют с диапазоном: arr : ARRAY[0..9] OF REAL - допустимые индексы 0…9, десять элементов. ARRAY[1..10] - индексы 1…10. Смешение стилов в одном проекте - источник ошибок: модуль A пишет с индексом 1, модуль B читает с 0.

Функции границ: ARRAY_LOWER_BOUND, ARRAY_UPPER_BOUND (или LBound/UBound в некоторых диалектах). Для динамического размера в ST 3.5 - ARRAY_LEN / размер при объявлении фиксирован.

Индексация в FOR должна совпадать с объявлением. FOR i := ARRAY_LOWER_BOUND(arr) TO ARRAY_UPPER_BOUND(arr) - безопасный шаблон при смене размера массива в одном месте объявления.

Обращение arr[i] при i вне диапазона в рантайме CODESYS на целевом ПЛК часто приводит к останову задачи или исключению. Симулятор иногда мягче - не полагайтесь на «в симуляторе не падало».

FOR с неверной границей: классические промахи

TO LENGTH вместо TO LENGTH - 1.
Если массив ARRAY[0..9], LENGTH = 10. Цикл FOR i := 0 TO 10 даёт i = 10 на последней итерации - выход за границу.

Цикл с 1 при нулевой индексации.
FOR i := 1 TO 9 для ARRAY[0..9] - пропуск arr[0] и логическая ошибка; FOR i := 1 TO 10 - падение на i = 10.

Отрицательный индекс после вычитания.
Index := Selected - 1 при Selected = 0 на HMI (оператор не выбрал строку) даёт -1.

Шаг STEP с нарушением границы.
Редко, но FOR i := 0 TO 8 STEP 2 для массива длины 5 - последний i может выйти за UPPER_BOUND при неверном TO.

Вложенные FOR с общим именем i.
Внутренний цикл сбрасывает i внешнего - после внутреннего цикла i непредсказуемо, следующее arr[i] падает.

Шаблон: верхняя граница только через ARRAY_UPPER_BOUND(arr), не «зашитое» число 9, если размер массива меняется при рефакторинге.

Пустой массив и динамическая длина

После операции «очистить список рецептов» или «удалить все строки» LENGTH может стать 0. Код FOR i := 0 TO ARRAY_UPPER_BOUND(arr) при фиксированном объявлении ARRAY[0..9] UPPER_BOUND всё равно 9, но логическая «длина данных» 0 - обход всех ячеек читает старый мусор. Если используется переменная ValidCount и цикл FOR i := 0 TO ValidCount без проверки, при ValidCount = 0 цикл 0 TO 0 выполняется один раз - нужен IF ValidCount > 0 THEN.

Для «пустого» сценария явная ветка:

IF ValidCount = 0 THEN (* не входить в цикл, дефолт *) ELSE FOR i := 0 TO ValidCount - 1 DO ... END_FOR END_IF

На HMI пустой список и индекс «-1» или «0» без выбора - валидировать до передачи в ПЛК.

Данные с HMI: индекс без проверки

Типовой путь: ListBox / ComboBox передаёт номер строки в ModeIndex, RecipeNo, AxisSelect. Оператор или баг экрана шлёт 255, -1, 999. Программа: Params[ModeIndex] := HMI_Value - fault.

Защита на ПЛК (обязательно, HMI не единственный барьер):

IF ModeIndex >= ARRAY_LOWER_BOUND(Params) AND ModeIndex <= ARRAY_UPPER_BOUND(Params) THEN ActiveParam := Params[ModeIndex]; ELSE ActiveParam := DefaultParam; Alarm_InvalidIndex := TRUE; END_IF

На HMI: ограничить список, не давать ручной ввод индекса без роли наладчика, синхронизировать количество строк списка с константой в общем include.

Связка с ролями доступа на панели оператора: ручной ввод индекса - только наладчик, с подтверждением.

При Modbus-карте экрана индекс может приходить как регистр со стороны SCADA - та же проверка на ПЛК. См. Modbus-карта экрана и Bad quality - если quality плохое, индекс может остаться последним good и быть неверным логически.

Диагностика онлайн на объекте

В момент fault открыть Online приложение, найти строку в call stack / exception. Смотреть переменные: индекс, LENGTH, UPPER_BOUND, источник с HMI.

Force индекса с экрана в онлайн без изменения HMI - воспроизвести падение на безопасном стенде.

Включить логирование индекса перед обращением к массиву в debug-сборке (условная компиляция) - на объекте один цикл с записью в ring buffer.

Сравнить поведение симулятора и ПЛК: в симуляторе индекс мог не приходить с HMI, список на экране был короче.

После загрузки без retain проверить, не остался ли индекс из прошлой сессии в RAM при частичной инициализации - редко, но ModeIndex без сброса в INIT при старте.

Отличие от NaN и переполнения

ST: результат NaN, деление и переполнение - fault на делении 0, INF, REAL overflow. Симптом: «программа сошла с ума», значения на экране неверные, но call stack указывает на арифметику, не на array[i].

Проверка: exception message / номер строки. NaN часто продолжает выполнение с poisoned REAL; индекс массива - жёсткий stop.

Отличие от ENUM после загрузки

ENUM и CASE после загрузки: симулятор и объект - переменная ENUM вне допустимых значений после download, CASE не покрывает ветку. Логика идёт в неверную ветку, индекс может вычисляться косвенно (ORD(enum)) и выйти за границу массива, но первичная причина - ENUM, не FOR.

Диагностика: смотреть значение ENUM в онлайн, не только индекс. Инициализация ENUM в каждом POU после старта.

Индекс как результат другого массива или поиска

Index := FindSlot(Array) возвращает -1, если не найдено. Следующая строка Data[Index] - падение. Паттерн: IF Index >= 0 THEN.

Поиск максимума в пустом наборе - MaxIndex не инициализирован, 32767 на INT - выход за границу.

Косвенная индексация: Matrix[Row][Col] - обе координаты валидировать.

Условие, выход за границу и как поймать до пуска

Условие Как выходит за границу Как поймать до пуска
FOR i := 0 TO LENGTH i = LENGTH на последней итерации Статический review; шаблон TO UPPER_BOUND
FOR i := 1 TO N при ARRAY[0..N-1] i = N Сверить LOWER/UPPER с циклом
Индекс с HMI без clamp Оператор / SCADA шлёт вне списка Тест всех граничных значений +1/-1
Selected - 1 при Selected = 0 Индекс -1 Unit-тест ModeIndex 0 и min списка
ValidCount = 0, FOR 0 TO ValidCount Одна итерация с i=0 Тест очистки списка на HMI
FindSlot = -1 без проверки Data[-1] Тест «не найден» в таблице
Вложенные FOR, общий i i после внутреннего цикла Разные имена переменных цикла
ARRAY[ORD(Enum)] без валидного ENUM ORD вне таблицы CASE Тест после download все ENUM
Константа размера устарела Расширили массив, цикл старый Один источник размера - константа
Симулятор без HMI Индекс всегда 0 FAT с реальным экраном и списками
Retain индекса после смены программы Старый индекс, новый размер массива INIT сброс или retain-версия структуры

Статический анализ и код-ревью

Перед пуском: grep по проекту обращений к массивам с переменным индексом. Каждое место - есть ли IF с границами.

Код-ревью чек: нет ли TO LENGTH; все индексы с HMI/Modbus проверены; Find/Search с -1 обработан.

В CODESYS Analyzer / подобных инструментах - предупреждения о потенциальном выходе за границы, если доступны для версии.

Константа MAX_MODES и массив ARRAY[0..MAX_MODES-1] - HMI список не длиннее MAX_MODES.

Паттерны безопасного доступа

Clamp: Index := LIMIT(LOW, Index, HIGH) перед использованием - не лечит логику, но предотвращает fault; лучше alarm при clamp.

Указатель на элемент с проверкой: только после IF в границах.

Отдельная функция GetParam(Index) с return status BOOL - вызывающий код не обращается к массиву при FALSE.

Не дублировать границы «магическими числами» в пяти POU - один include GVL_Constants.

Связь с рецептами и версиями

Оператор загружает рецепт с 15 строк, программа ожидает 10 - индекс строки с HMI 14. Версия рецепта в заголовке и проверка RowIndex < Recipe.RowCount до доступа. Соседняя тема - оператор запустил не тот рецепт, там акцент на версии, здесь - на индекс строки внутри.


Типовые ошибки

Полагаться на HMI «там не выберут больше».
SCADA, скрипт, баг экрана - индекс приходит на ПЛК. Проверка только на ПЛК.

Тест только на симуляторе.
HMI не подключён, индекс 0 - все тесты зелёные.

LENGTH и UPPER_BOUND перепутаны.
Для ARRAY[0..9] UPPER=9, LENGTH=10 - цикл TO 10 падает.

Игнорировать warning компилятора.
Иногда компилятор видит статический выход - редко для динамического индекса.

Вопросы с объекта

Почему падает только на объекте, в офисе нет?
Реальный HMI, оператор, retain, другая версия списка на экране.

Можно ли отключить проверку границ в рантайме?
Не рекомендуется. Лучше валидация и alarm.

Падение в библиотеке - мой код не трогал массив.
Индекс передали в библиотечную функцию - смотрите вызывающий код.

INT16 индекс с HMI как UINT
65535 в знаковом INT - отрицательное или огромное после преобразования - валидировать тип.

На практике

В проектах на CODESYS 3.5 для ПЛК с Linux RT полезно держать общий модуль ArrayGuards с функциями проверки индекса и единым кодом аварии ALM_ARRAY_INDEX. Среда CODESYS и MasterSCADA 4D на одном железе взаимозаменяемы - логика валидации индекса в ПЛК не зависит от того, с какого HMI пришёл номер строки.

TOPICS • ТЕМЫ#Structured Text массив#индекс массива ST#FOR границы CODESYS#array index out of bounds#HMI индекс ПЛК#IEC 61131-3 массив#диагностика ST онлайн