Как взорвать ракету одной переменной
vk f t

Как взорвать ракету одной переменной

Краткий мастер-класс по правильному объявлению типов данных.

Что произошло

Ошибки в коде бывают у всех, но их последствия всегда разные. Одно дело — ошибиться в программе для шагомера и потерять 500 шагов, и другое — лишиться полумиллиарда долларов.

Эта история произошла в 1996 году, когда Европейское космическое агентство потеряло космическую ракету-носитель Ariane-5 из-за неправильно объявленной переменной. Через 40 секунд после старта ракета взорвалась в воздухе, полностью уничтожив четыре спутника, которые нужно было вывести на орбиту.

Когда комиссия начала расследование, она выяснила, что во всём виновата одна переменная, которая не могла принимать значение больше, чем 32 767. Этого оказалось достаточно, чтобы всё вышло из-под контроля.

Ракета-носитель Ariane-5, фото из Википедии

В чём причина

В летательном аппарате был модуль, который отвечал за ориентировку в пространстве, — инерционная система ориентировки, ИСО. Европейцы ставили этот элемент на все ракеты, допиливая его под разные задачи. Ariane-5 достался модуль от Ariane-4. Думали, что раз он прекрасно показал себя на предыдущем аппарате, то и на новом тоже всё заработает.

Ракета Ariane-4 летала не так быстро, как Ariane-5, и модуль мог спокойно посчитать всё, что ему было нужно, используя переменные в диапазоне от −32 768 до 32 767. Причём этот промежуток был с запасом: в реальности такие числа на Ariane-4 не использовались.

Но Ariane-5 летала намного быстрее. Переменные очень скоро достигли своего максимального значения и вызвали сбой. Включился резервный модуль, но в нём стоял точно такой же софт от старой ракеты, который сделал всё то же самое: быстро достиг потолка в переменной и тоже вызвал сбой. Ariane-5 продолжала ускоряться.

Через 40 секунд после старта ракета набрала такую высокую скорость, что аэродинамическая нагрузка стала критической: обшивка начала разрушаться, стартовые двигатели отделились, и аппарат взорвался.

Почему переполнилась переменная?

Память у компьютеров не резиновая. Когда мы пишем программу и нужно в ней что-то запомнить, мы говорим компьютеру: «Выдели нам память для такой-то информации».

Есть языки программирования, где место для данных выделяется автоматически. Например, в JavaScript просто говоришь: «Вот новая переменная, положи в неё вот это», — и система сама определит, сколько на эту информацию нужно памяти. Есть языки, где необходимо чётко сказать, какого типа переменную следует создать. Например, в языке C:

int speed;
speed = 99;

Int означает integer, то есть целое число. На целое число выделяется сколько-то памяти, в зависимости от языка. Чтобы не погружаться в битность и сложности двоичного счисления, представьте такую ситуацию.

Вы делаете инвентаризацию товаров на складе. По каждой позиции вы заполняете форму, в которой есть поле «Порядковый номер». В этой графе три пустых клетки, которые вы можете заполнить цифрами от 0 до 9. Если начинать с товара 001, то максимальное количество позиций, которое вы можете посчитать с помощью этих трёх клеток, — 999.

[0] [0] [1]

[0] [0] [2]

[0] [0] [3]

...

[9] [9] [9]

Если у вас на складе лежит более 999 товаров, вам потребуется какая-то другая форма, в которой будет уже не три, а четыре клетки. Или можно объявить программный сбой и уйти на обед.

Вот это и произошло с ракетой:

  1. Ракете дали, грубо говоря, 16 клеток, чтобы записать какой-то параметр скорости.
  2. Максимальное число, которое можно было записать на компьютере, используя 16 компьютерных клеток, — 65 535.
  3. Если нам нужны числа с плюсами и минусами, то мы получаем диапазон значений от −32 768 до +32 767.
  4. Всё, что выходит за рамки этого промежутка, компьютер уже не может записать — так же, как мы не можем внести в три клетки число более 999.
  5. Программе нужно было записать число больше, чем 32 767. А клеток не хватает!
  6. Дальше вы знаете.

Вот так одна строка кода может пустить на ветер кучу денег и стать одной из самых дорогих ошибок в истории программирования. А вот та самая строка:

P_M_DERIVE(T_ALG.E_BH) := UC_16S_EN_16NS (TDB.T_ENTIER_16S

                                  ((1.0/C_M_LSB_BH) *

                                  G_M_INFO_DERIVE(T_ALG.E_BH)))

Ещё по теме