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


image

Вот пример кода на языке Structured Text, который применяется для программирования промышленных контроллеров. Програм��ы на этом языке могут выполнять различные действия, от управления светофором до контроля процессов на атомной электростанции. Специфика языка и платформы соблюдена, так что код может выглядеть достаточно непривычно.


VAR
   i: INT;
   OUT: INT;
   IN: ARRAY [0..4] of INT:= 1, 2, 3, 4, 5;
END_VAR
OUT := 0;
FOR i:= 0 TO 4 DO
   OUT := OUT + IN[i];
END_FOR;

Что делает эта программа? Читает данные с входных сигналов и записывает управляющее воздействие в переменную OUT. Значение этой переменной будет 15. Теперь внесем некоторую ошибку в программу, а конкретно взятие элемента по несуществующему индексу в массиве и посмотрим на результат.


VAR
   i: INT;
   OUT: INT;
   IN: ARRAY [0..4] of INT:= 1, 2, 3, 4, 5;
END_VAR
OUT := 0;
FOR i:= -1 TO 4 DO
   OUT := OUT + IN[i];
END_FOR;

Значение в переменной OUT = 15

То есть такой же, как и до ошибки. Программа не бросила исключение, а просто проигнорировала несуществующий элемент. Такое поведение для нас вполне приемлемо, если мы случайно не обработали исключение и управляем аварийными стержнями ядерного реактора. Управляющее воздействие переменной OUT будет таким же, как и до ошибочного изменения индекса. Если это кратковременный сбой, то система не отреагирует на ошибку и будет дальше стабильно продолжать работать. Теперь представим, что эту же задачу выполняют разные программы на других языках программирования и сравним результаты.


Код без ошибочного индекса выполняется одинаково на всех языках программирования и в переменной OUT всегда значение 15. Будем рассматривать только тот код, где закралась ошибка со значением стартового индекса равном -1.


JavaScript


var IN = [ 1, 2, 3, 4, 5 ];
var OUT = 0;
for (var i = -1; i <= 4; i++) {
  OUT += IN[i];
}
  console.log('OUT = '+ OUT);

OUT = NaN

Go


package main

import "fmt"

func main() {
    IN := [5]int{ 1, 2, 3, 4, 5 }
    OUT := 0
    for i := -1; i <= 4; i++ {
        OUT += IN[i]
        }
     fmt.Printf("OUT = %d", OUT)
}

panic: runtime error: index out of range

Java


public class MyClass {
    public static void main(String args[]) {
    int[] IN = {1, 2, 3, 4, 5};  
    int OUT = 0;
    for (int i = -1; i <= 4; i++)
         OUT += IN[i];
     System.out.printf("OUT = %d", OUT);     
    }
}

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1

PHP


<?php
$IN = [1, 2, 3, 4, 5];
$OUT = 0;
for ($i=-1; $i<=4; $i++) {
    $OUT += $IN[$i];
}
echo('OUT = '.$OUT);

OUT = 15
PHP Notice: Undefined offset: -1

Python 3


IN = [ 1, 2, 3, 4, 5 ];
OUT = 0;
for i in range(-1, 5):
  OUT += IN[i];
print('OUT = {0:1d}'.format(OUT));

OUT = 20

В Python поддерживаются отрицательные индексы и нумерация идет с конца массива.


C/C++


#include<stdio.h>

int main() {
   int IN[]= {1,2,3,4,5};
   int OUT=0;
   int i; 
   for (i=-1; i<=4; i++) {
    OUT += IN[i];
   } 
   printf("OUT = %i", OUT);
   return 0;
}

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


http://codepad.org


OUT = -143484461

https://ideone.com/ и др.


OUT = 15

https://www.jdoodle.com


OUT = 14

В рамках этой статьи я не буду вдаваться в подробности, какие компиляторы С/С++ на каких платформах используют эти сайты. Буду очень рад, если вы поделитесь своим мнением в комментариях.


C Sharp


using System;

class Program
{
    static void Main()
    {
        int[] IN = new int[] { 1, 2, 3, 4, 5 };
        int OUT = 0;
        for (int i = -1; i <= 4; i++)
        {
            OUT += IN[i];
        }

        Console.Write("OUT of IN + y = "+ OUT);
    }
}

Unhandled Exception:
System.IndexOutOfRangeException: Index was outside the bounds of the array.

Выводы


При написании кода на любом языке программирования нужно учитывать особенности работы со структурами данных в языке. Также учесть все места в коде, где программа может "упасть". Часто после исключения программный код уже невозможно вернуть на строку возникновения ошибки, а ниже по коду могут выполняться важные действия. Поэтому не все языки подходят для решения определенных задач или требуют дополнительных проверок и ветвлений кода. В языках стандарта МЭК есть защита от примитивных программных ошибок, так как зачастую перегрузить программу контроллера при сбое некому и эта операция также может оказаться фатальной в некоторых ситуациях.