Pull to refresh

Доступ к несуществующему индексу массива

Reading time 3 min
Views 16K

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


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.

Выводы


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

Tags:
Hubs:
-30
Comments 40
Comments Comments 40

Articles