Итак, Новый Год, заняться особенно нечем, а давайте я вас чуть-чуть "подразню" что-ли? Покажу вам на простых примерах некоторые возможности одного из языков программирования, который многие считают устаревшим. Не вопрос, считайте дальше, на данный момент мы пользуемся Стандартом 2005-го года, т.е., новее, чем у С++, к 2012 готовится очередная версия Стандарта.
Но, собственно, я не собираюсь разжигать здесь холивар, максимум - пробудить интерес. Если кто-то заинтересуется - уже хорошо, если нет - то будем считать, что это все написано, чтоб провести время, не просто так смотря в монитор...
Итак.
С чего начнем? Наверное, с Hello World? Нет, не интересно. Напишем хоть сколько-нибудь полезную программку. Пускай она получает от пользователя число, и определяет, положительное оно или отрицательное:
(Паскаль)
var i: integer; begin write('i = '); readln(i);
if i > 0 then writeln('positive') else writeln('negative or zero'); end.
(Ада)
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Text_IO; use Ada.Text_IO; procedure Hello is i: integer; begin Put("i = "); Get(i);
if i > 0 then Put_Line ("positive"); else Put_Line("negative or zero"); end if;
end;
Что бросается в глаза? "Многословность". Похоже, это - единственный недостаток Ады
Давайте теперь немного поговорим о преимуществах.
0. Описание переменных по мере необходимости.(Показать/Скрыть)
Та вещь, которой я всегда завидовал в С++, и которая присутствует в Аде: описание переменных там, где они нужны, а не там, где описан заголовок функции/процедуры. В любом месте программы можно добавить
declare -- Описание переменной begin -- Ее использование end;
, после закрытия блока (после оператора end) переменная выходит из области видимости и попытка обратиться к ней приводит к ошибке компиляции.
1. Циклы For.(Показать/Скрыть)
Все, наверное, встречали типовую ошибку начинающего программиста (особенно это касается Турбо-Паскаля, где эта ошибка никак не отлавливается) - изменение управляющей переменной цикла в самом цикле. И еще одна: обращение к переменной цикла после его окончания. Ну, скажем:
var i: integer; begin for i := 1 to 10 do begin writeln(i); inc(i); end; end.
Знакомая картина, правда? Хотя в описании языка ясно сказано: подобное изменение - это ошибка! Чуть лучше дело обстоит в 32-битных компиляторах, там подобный код компилироваться не будет. Однако вариант языка Ada мне нравится еще больше:
procedure Hello is begin for i in 1 .. 10 loop -- i вообще не описывается Put(i); New_Line; end loop; end;
, то есть мало того, что i - вообще не описывается (в самом деле, зачем? Что, компилятор по типу индексов не сможет определить, какую переменную использовать для их перебора? Сможет...), а раз не описывается, то все вопросы типа "Что такое? Переменная есть, а изменить нельзя..." отпадают. Нет переменной и все тут так еще и видимость i ограничена только текущим блоком for loop ... end loop, то есть, обращение к ней после end-а вообще лишены смысла.
Кстати, для того, чтобы заставить компилятор сделать "обратный цикл" достаточно просто:
for i in reverse 1 .. 10 loop -- добавить reverse
2. Проверка логических условий.(Показать/Скрыть)
Вот еще одно слабое место Паскаль-программ:
if (условие_1) and (условие_2) then ...
Если условие_1 не выполняется, то при вычислении условия_2 может произойти ошибка времени исполнения (скажем, обращение по нулевому указателю или попытка деления на 0) при полном вычислении логических условий, поэтому для избежания подобных ошибок приходится "оборачивать" условия в директиву {$B-} ... {$B+}, чтобы гарантировать "быстрое" вычисление, при этом если условие_1 вернет false, то программа не станет вычислять условие_2, и, следовательно, ошибки не произойдет.
В Ada для решения подобной проблемы есть расширенный синтаксис оператора условия:
if (условие_1) and then (условие_2) then ...
, при этом вычисление условия_2 будет производиться только если условие_1 истинно. Для выражений, разделенных or, расширенный синтаксис используется в виде
if (условие_1) or else (условие_2) then ...
То есть, все делается средствами языка, никаких зависимостей от настроек среды или от версии компилятора...
3. Оператор Goto(Показать/Скрыть)
Какой основной довод приводят для того, чтобы оправдать использование goto? В частности - простой выход из нескольких вложенных циклов. Вот, например:
function f(i, j, k: integer): boolean; begin f := (i + 10*j - 3*k > 20); end;
var i, j, k: integer; label finish; begin for i := 1 to 10 do for j := 1 to 10 do for k := 1 to 10 do begin writeln(i + j + k); if f(i, j, k) then goto finish; end; finish:; end.
Если не использовать goto, то выйти из всех трех циклов через Break - невозможно, придется менять циклы на While или Repeat, и усложнять условия + добавлять еще флаги...
Ада предлагает другой вариант - именованные циклы и более мощные exit-ы:
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Hello is function f(i, j, k: integer) return Boolean is begin return ((i + 10*j - 3*k) > 20); end;
begin first_loop: for i in 1 .. 10 loop for j in 1 .. 10 loop for k in 1 .. 10 loop Put(i + j + k); exit first_loop when f(i, j, k); end loop; end loop; end loop first_loop; Put_Line("Oops..."); end;
В Турбо Паскале ими и не пахнет, хотя создан был компилятор Ada-83 еще раньше чем финальные версии Турбо Паскаля. В 32-битах оно есть, но вот пример, как хочется их использовать, потому что это удобно:
procedure f(a: integer = 10; b: real = 20.0; s: string = 'Ok'); begin writeln(a:5, b:10:4, s:15); end;
begin f(); // Здесь все ясно: все параметры - по умолчанию f(11); // Здесь - меняется первый параметр, остальные - по умолчанию f(10, 14.0); // <--- Ну я же не хочу менять первый параметр, почему его надо повторять??? f(10, 20.0, 'Error'); // <--- А здесь повторяется уже 2 default-значения. Зачем? end.
Сравниваем:
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure Hello is procedure f(a: Integer := 10; b: Float := 20.0; s: String := "Ok") is begin Put(a); Put(b); Put(s); New_Line; end;
begin f; -- f(11); -- f(b => 14.0); -- меняется только b f(s => "Error"); -- и только s ... end;
5. Инициализация массивов.(Показать/Скрыть)
Как, к примеру, описать массив целых из 100 элементов и заполнить его так, чтобы первые 22 элемента были тройками, потом еще 7 пятерок, а все остальные - четверки? Просто, правда? Или описать все значения явно (ну хорошо, а что делать если все умножить на 10, т.е., не 100 а тысяча элементов?) или сделать небольшой цикл (или три, в зависимости от настроения) который будет это заполнять. Но погодите, мы же используем не Паскаль... Ну-ка...
with Ada.Text_IO; use Ada.Text_IO; with Ada.Integer_Text_IO; use Ada.Integer_Text_IO; with Ada.Float_Text_IO; use Ada.Float_Text_IO;
procedure Hello is
subtype arrIndex is Integer range 1 .. 100; type arrType is array(arrIndex) of Integer;
begin for i in arrIndex loop Put(arr(i)); end loop; end;
- еще на этапе трансляции... То же самое касается и присвоения значений записям и объектам. Кстати, вариантных полей (селекторов) в записи может быть несколько, в отличие от Паскаля, где все ограничено одним Case-полем.
6. Еще немного о массивах.(Показать/Скрыть)
Разумеется, все сталкивались с подобной проблемой. Допустим, вы передаете в подпрограмму размер буфера, и в самой подпрограмме вам нужен буфер именно такого размера. В таком случае пользователю Паскаля ничего не остается, как использовать динамические массивы:
Procedure P(size: Integer); Var Arr: array[1 .. size] of Integer; Begin // тут работаем с Arr End;
Это не будет компилироваться (даже если size описать так: Procedure P(Const size: Integer);). Нужно либо использовать Array of Integer + SetLength, либо GetMem или подобные средства выделения памяти. Что касается Ады:
procedure P(size: integer) is arr: array(1 .. size) of Integer; begin -- тут работаем с Arr end P;
это совершенно легальная конструкция, которая прекрасно работает - массив arr будет содержать ровно size элементов. Более того, можно сделать еще красивее (с использованием блока declare, о котором я упоминал выше):
-- здесь каким-то образом вычисляем -- или получаем от пользователя size declare -- описываем локальный для блока declare массив arr: array(1 .. size) of Integer; begin -- тут работаем с arr end; -- а тут массива уже не существует, и обращаться к нему нельзя ...
(чем не "сборка мусора"?)
7. Параметры подпрограмм.(Показать/Скрыть)
В Ada программист не должен описывать метод передачи данных в подпрограмму (то есть, передается ли параметр по значению, или по ссылке - Var, или по константной ссылке - Const), это решает компилятор, который прекрасно знает размеры всех типов (вот они, преимущества строгого описания типов) и сам решает, как передавать данные.
Программист задает только уровень доступа к параметрам: или только для чтения (in), или только для записи (out, при этом формальный параметр вообще не инициализируется значением фактического параметра, а фактический - получает значение, присвоенное формальному внутри процедуры), или аналог Var-параметров (in out, формальный параметр инициализируется фактическим, и все его изменения будут доступны "снаружи") или access-режим, введенный в стандарте Ada95 для передачи по ссылке.
При этом малейшая попытка изменить значение in-параметра (помните, в Паскале постоянная практика - параметр переданный по значению, можно менять, все равно его изменение назад не передается) карается прекращением компиляции. Это - параметр только для чтения. Менять его нельзя. Так же наказывается попытка выйти из подпрограммы без инициализации out-параметра. Если такой параметр есть - надо его инициализировать, иначе в вызывающем блоке могут начаться проблемы, чего Ада со своей повышенной безопасностью допустить не может.
Кстати, вот еще одно преимущество Ada: Это полностью стандартизированный язык. И такого, как происходит с Паскалем - "что хочу, то и ворочу" (в зависимости от компилятора) просто не может быть. В первую очередь программа должна соответствовать Стандарту, и если она ему соответствует, то гарантируется ее одинаковая работа на всех трансляторах.
8. Дженерики.(Показать/Скрыть)
Ада предоставляет программисту возможность использовать Дженерики. Причем то, что недавно стало возможным в FPC 2.2.0 - лишь жалкое подобие тех возможностей, которые есть в Ada. Ну, скажем, описание шаблонных функций, инициализация шаблонного пакета не типом, а некоторым значением или вообще функцией, что невозможно (будем надеяться, пока) в Паскале/Дельфи.
Немного о безопасности
Недавно мне задали вот такой (очень, казалось бы, простой) вопрос: "Везде, где написано про язык программирования Ада, есть утверждение, что он - более безопасный, чем тот же С/С++. А в чем это выражается?"
А давайте попробуем посмотреть в чем это выражается (в основном сравнение происходит с С-подобными языками, Паскаль может не иметь многих из нижеперечисленных недостатков)...
1. Пример программы на С
#include <stdio.h> int main () { int length = 8265; int width = 0252; int height = 8292; printf ("length = %d\n", length); printf ("width = %d\n", width); printf ("height = %d\n", height); }
Программа прекрасно компилируется и запускается на выполнение... Но каким будет ее вывод?
Вот таким будет вывод:(Показать/Скрыть)
length = 8265 width = 170 height = 8292
по той простой причине, что в С/С++ числа, начинающиеся с 0 трактуются как записанные в Octal (8-ричной системе счисления). Это очень трудно обнаруживается программистом, и является потенциальным источником ошибки.
Плюс к этому, если уже есть префиксы и для 16-ричной системы счисления (0x) и для 8-ричной (0), то почему забыта двоичная система? Как в С/С++ задать значение в двоичной системе? А в троичной?
Для сравнения:
with Text_IO; procedure Main is Length : Integer := 8265; Width : Integer := 0252; Height : Integer := 8292; begin Text_IO . Put_Line (Length'img); Text_IO . Put_Line (Width'img); Text_IO . Put_Line (Height'img); end Main;
не приводит ни к каким сюрпризам:
8265 252 8292
, ведущие нули просто игнорируются. Что касается задания числа в другой системе счисления - нет проблем, в любой, с основанием от 2 до 16:
-- можно отделять тысячи/миллионы/... друг от друга (для удобства программиста) Length : Integer := 8_265; -- обычное десятичное число Width : Integer := 0252; -- восьмеричное число Width_8 : Integer := 8#252#; -- двоичное число (можно разделять тетрады символом подчеркивания, для удобства) B_Mask : Integer := 2#1100_1011#; -- 16-ричное число W_Mask : Integer := 16#FFF1_A4B0#; -- Система счисления с основанием 7 Strange : Integer := 7#12345#;
2. Корректен ли следующий код:
#include <limits.h>
/* Если *y - ноль и x > 0, то *k приравнять максимальному положительному числу Если *y - ноль и x <= 0, оставить *k без изменений Если *y - не ноль, присвоить *k значение x деленного на *y, и увеличить *y на 1 */ void check_divide (int *k, int x, int *y) { if (*y = 0) if (x > 0) *k = INT_MAX; else *k = x / *y /* Здесь делить на *y безопасно, поскольку нулю оно не равно */; *y++; }
? Несмотря на то, что программа компилируется, здесь присутствуют как минимум 4 серьёзные проблемы. Какие?
...(Показать/Скрыть)
Проблема №1 Смешивание "=" и "==". "=" представляет собой присваивание, тогда как "==" - это сравнение. Очевидно, должно использоваться именно сравнение. Ну, а поскольку многие программисты отключают все Warning-и, то даже подсказка компилятора останется незамеченной. (Один из способов решения этой проблемы, который сработает не всегда, а при сравнении с константой, заключается в записи: if(0 = *y) ... , что позволит компилятору определить ошибку, но в случае если сравнивать надо со значением другой переменной, этот способ уже не спасет)
Проблема №2 "Смещённый" else, относящийся не к первому if-у, как может показаться, а ко второму. Компилятором не отслеживается. Придётся "оборачивать" все конструкции фигурными скобками...
Проблема №3 Аналогична второй, увеличение y не относится к ветви else, а выполняется безусловно. Опять оборачиваем блок операторов фигурными скобками...
Проблема №4 Неправильный приоритет операций: выражение *y++ будет выполняться как *(y++), а не (*y)++, как Вам хотелось бы. Придётся указывать порядок выполнения скобками, или отделять операцию инкремента пробелом: *y ++.
Итоговый вариант:
include <limits.h> void check_divide (int *k, int x, int *y) { if (*y == 0) { if ( x > 0) *k = INT_MAX; } else { *k = x / *y; (*y)++; /* или *y ++ */ } }
Для сравнения, та же программа на Аде:
procedure Check_Divide (K : in out Integer; X : Integer; Y : in out Integer) is begin if Y = 0 then if X > 0 then K := Integer’Last; -- K приравниваем наибольшему значению для целого end if; else K := X / Y; -- Деление на Y безопасно, он не может быть равен 0 Y := Y + 1; end if; end Check_Divide;
, или даже:
procedure Check_Divide (K : in out Integer; X : Integer; Y : in out Integer) is begin if Y = 0 and then X > 0 then K := Integer’Last; elsif Y /= 0 then K := X / Y; Y := Y + 1; end if; end Check_Divide;
То есть: 1) невозможно "перемешать" оператор присваивания (:=) и оператор сравнения (=), поскольку выражение становится некорректным синтаксически, компилятор его не пропускает; 2) проблемы "смещённого" else в Аде не существует, каждый оператор if обязан закончиться end if, иначе будет ошибка времени компиляции; 3) то же самое касается и "безусловно" выполняемого выражения - все то, что предшествует end if, относится к условию; 4) поскольку операции разыменования в языке нет, то проблема снимается сама собой.
P.S. С++ чуть более безопасен в этом плане, если передать y по ссылке, то одной проблемой будет меньше
3. Что касается ассоциативности и приоритета операций:
Что означает условие
if (x & mask == 0) ...
?
Оно компилируется, но...(Показать/Скрыть)
не означает if ((x & mask) == 0), а означает if (x & (mask == 0)), компилятор не в состоянии помочь программисту найти ошибку из-за неоднозначности
А вот это:
if (x < y < z) ...
?
И это тоже компилируется, но...(Показать/Скрыть)
не означает if ((x < y) && (y < z)), а означает if (((x < y) && (1 < z)) || (0 < z)) И здесь компилятор бессилен...
Налицо не совсем ожидаемое поведение.
Что касается Ады (и других Паскаль-подобных языков):
if x and mask = 0 … -- выдаст ошибку при компиляции
Требуется расставить скобки, чтобы это откомпилировать. А значит, опечатка программиста или просто невнимательность - исключены. Больше того, поскольку подобная операция со знаковыми числами может быть небезопасной, Ада запрещает производить ее с теми операндами, которые не являются переменными типа Unsigned_xx
if x < y < z ... -- Аналогично: ошибка компиляции
Требуется уточнение условия: if (x < y) and (y < z) …
4. Еще о синтаксисе...
Корректен ли следующий код:
// -- Если впереди "зелёный" свет - увеличить скорость void increase_speed_if_safe (int speed, int signal) { if (signal == CLEAR); increase_speed (); }
?
Код компилируется без предупреждений, но...(Показать/Скрыть)
Лишняя точка с запятой, в результате, независимо от значения signal, скорость будет увеличена. К чему это может привести ;) ?
Для сравнения:
-- Если впереди "зелёный" свет - увеличить скорость procedure increase_speed_if_safe (speed : integer; signal : integer) is begin if signal = CLEAR then; -- Ошибка времени компиляции increase_speed; end if; end increase_speed_if_safe;
5. Поговорим о перечислениях...
enum Alert_Type {LOW, MEDIUM, HIGH, VERY_HIGH};
void handle_alert (enum Alert_Type alert) { switch (alert) { case LOW: activate_camera (); case MEDIUM: send_guard (); case HIGH: sound_alarm (); } }
void process_alerts () { handle_alert (2); ...
Программа компилируется, но выполняться будет совсем не так, как кажется. В чем дело?
А дело в том, что...(Показать/Скрыть)
1) после окончания каждой ветки case требуется добавление break, чтобы выйти из оператора switch, иначе будут выполняться коды всех меток, расположенных ниже, до первого break-а или до окончания оператора switch...
Кстати, по поводу того, что для завершения текущей ветки нужны break-и: несколько лет назад компания Sun Microsystems провела исследования Сишных программ на предмет использования оператора switch. Выяснилось, что в 95% случаев после каждой ветки стоят break-и. То есть, "проваливание" использует только очень малый процент программистов. Подавляющее большинство вынуждено вносить в свою программу дополнительные операторы, чтобы получить более часто используемый функционал.
2) и С и С++ (да и Java тоже) успешно компилируют программу, несмотря на то, что остались неохваченные оператором switch возможности (в лучшем случае выдается предупреждение). В приведенном выше случае, что будет, если мы передадим в функцию значение VERY_HIGH? 3) в С элементы перечисления являются обычными целочисленными значениями, и даже вызов handle_alert(6) успешно пройдет компиляцию, несмотря на то, что в перечислении столько значений просто нет...
Правильный вариант:
void handle_alert (enum Alert_Type alert) { switch (alert) { case LOW: activate_camera (); break; case MEDIUM: send_guard (); break; case HIGH: sound_alarm (); break; case VERY_HIGH: alert_police (); break; } } void process_alerts () { handle_alert (HIGH); ...
Для сравнения:
type Alert_Type is (LOW, MEDIUM, HIGH, VERY_HIGH); procedure Process_Alert (Alert : Alert_Type) is begin case Alert is when LOW => Activate_Camera; when MEDIUM => Send_Guard; when HIGH => Sound_Alarm; when VERY_HIGH => Alert_Police; end case; end Process_Alert;
1) при вызове процедуры компилятором будут пропущены только значения типа Alert_Type; 2) нет необходимости в аналоге break для выхода из оператора case (в любом случае по окончании выполнения кода соответствующего when-селектора выполнение case будет завершено); 3) в случае, если будет упущен один из вариантов перечисления, компилятор выдаст ошибку; 4) гибкие селекторы - можно сделать и так:
procedure Process_Alert (Alert : Alert_Type) is begin case Alert is when LOW => Activate_Camera; when MEDIUM => Send_Guard; when HIGH | VERY_HIGH => -- или HIGH или VERY_HIGH Sound_Alarm; Alert_Police; end case; end Process_Alert;
и так:
procedure Process_Alert (Alert : Alert_Type) is begin case Alert is when LOW => Activate_Camera; when MEDIUM .. VERY_HIGH => -- значения MEDIUM, HIGH, VERY_HIGH Send_Guard; Sound_Alarm; Alert_Police; end case; end Process_Alert;
, и вот так:
procedure Process_Alert (Alert : Alert_Type) is begin case Alert is when LOW => Activate_Camera; when MEDIUM => Send_Guard; when others => -- все остальные случаи Sound_Alarm; Alert_Police; end case; end Process_Alert;
6. Неопределенности
Что здесь происходит:
{ int k = 0; int v [10]; k = v [k++]; }
?
А происходит здесь...(Показать/Скрыть)
неопределённое поведение. См. Стандарт языка С++ по поводу точек следования. Между двумя точками следования переменная не может изменить значение больше одного раза, а в вышеприведенном примере значение меняется дважды. Причем, компилятор на подобные вещи вообще не реагирует. Никак.
В Аде это запрещено синтаксисом.
7. Проблемы с системой типов языка.
Программа компилируется, но все равно в ней что-то не так:
typedef int Time; typedef int Distance; typedef int Speed;
// ... const Speed SAFETY SPEED = 120;
void increase_speed (Speed s);
// … void check_speed (Time t, Distance d) { Speed s = d/t; if (s < SAFETY_SPEED) increase_speed (t); }
void perform_safety_checks () { Time t = get_time (); Distance d = get_distance (); // … check_speed (d, t); }
Что именно?
Уууу... Да здесь целый букет...(Показать/Скрыть)
Проблема №1
if (s < SAFETY_SPEED) increase_speed (t); // <--- t - это время, а не скорость
Проблема №2
check_speed (d, t); // <--- обратите внимание на порядок параметров
С/С++ не может в данном случае помочь программисту понять, что совершена ошибка.
Понятно, что на Аде можно написать точно так же:
-- Некорректный код, только для примера !!! SAFETY_SPEED : constant Integer := 120; -- … procedure Increase_Speed (S: Integer);
procedure Check_Speed (T : Integer; D : Integer) is S : Integer := D / T; begin if S < SAFETY_SPEED then Increase_Speed (T); end if; end Check Speed;
procedure Perform_Safety_Checks is T : Integer := Get_Time; D : Integer := Get_Distance; begin -- … Check_Speed (D, T); end Perform Safety Checks;
Но у Ада-программ есть еще 2 линии защиты, которые отсутствуют в С/С++ 1) типы, определяемые пользователем; 2) ассоциации параметров.
Итак:
-- определяем 3 новых целочисленных типа type Time is range 0 .. 3_600; type Distance is range 0 .. 1_000; type Speed is range 0 .. 4_000;
SAFETY_SPEED : constant Speed := 120;
procedure Check_Speed(T : Time; D : Distance) is S : Speed := D / T; -- Ошибка компиляции: Это не 3 целых числа, это 3 разнотипных значения begin if S < SAFETY_SPEED then Increase_Speed(T); -- Ошибка компиляции: T не типа Speed !!! end if end Check_Speed;
procedure Perform_Safety_Checks is T : Time := Get_Time; D : Distance := Get_Distance; begin -- … Check_Speed (D, T); -- Ошибка компиляции: несоответствие типов параметров end Perform_Safety_Checks;
Вот так будет выглядеть корректная программа:
type Time is range 0 .. 3_600; type Distance is range 0 .. 1_000; type Speed is range 0 .. 4_000;
SAFETY_SPEED : constant Speed := 120;
procedure Check_Speed(T : Time; D : Distance) is S : Speed := Speed(Integer(D) / Integer(T)); -- явное приведение типов !!! begin if S < SAFETY_SPEED then Increase_Speed(S); -- Ok end if end Check_Speed;
procedure Perform_Safety_Checks is T : Time := Get_Time; D : Distance := Get_Distance; begin -- … Check_Speed (T, D); -- Ok end Perform_Safety_Checks;
8. Проблема переполнения.
Что произойдет в следующем фрагменте программы на С (или на С++):
#include <limits.h> void compute () { int k = INT_MAX; k = k + 1; }
?
...(Показать/Скрыть)
В С/С++ переполнение знакового/беззнакового целого не ловится (несмотря на то, что существует EXCEPTION_INT_OVERFLOW) по крайней мере на нескольких платформах, случиться может все что угодно. Как workaround предлагается нечто подобное: Implemented integer overflow class (Очень удобно, правда? )
В Аде каждый раз, когда происходит переполнение выбрасывается исключение (на всех поддерживаемых платформах). То же смое касается и попытки деления на 0, и выхода за пределы массива, ну, и так далее...
Disclaimer Только не надо говорить, что примеры-искусственные, и специально подобраны так, что Ада показана выигрышно. Попробуйте привести примеры, как избежать в С/С++ тех неоднозначностей, о которых я написал, чтобы дать возможность компилятору не пропустить ошибку или недочет программиста.
По большей части смысл данной темы - в том, чтобы показать, что большинство проблем связано с излишней "гибкостью" языка, в котором "разрешено все то, что не запрещено" (а запрещено явно очень немного вещей), что то же самое и даже гораздо большее можно сделать и без этих правил, разрешающих "всё и вся", с таким строгим синтаксисом, как у Ады.
Unconnected
4.01.2011 19:05
Фухх, осилил) С многим сам сталкивался, вот например кажется такой код в С++
int k; if (k=5) {...}
, и это компилировалось, насколько помню.. только непонятно, в чем заключалось условие, в успешном присваивании?
С условиями жизненно, помню, ловил такое долго) Правда, можно сделать if U1 then if U2 then ... , тогда точно будут по отдельности выполняться.
А вот такую штуку
var a:integer; m:array [1..a] of byte;
было бы неплохо в паскале завести, давно хочу)
Ну, я так понял, что во многом Ада даёт "защиту от дурака", и если не выделываться (ну типа if (a<b<c) - тут на ходу дотумкать можно, что компилятор может не понять правильно), то скорее всего всё будет хорошо. Ну и где надо она гибше, да.. с всеуничтожающим break хорошо придумали.
volvo
4.01.2011 23:47
Цитата
и если не выделываться (ну типа if (a<b<c) - тут на ходу дотумкать можно, что компилятор может не понять правильно), то скорее всего всё будет хорошо
Ну вот и приведи мне решение (хочешь - на Паскале, хочешь - на Сях, хочешь - на С++), которое позволит мне на этапе компиляции забраковать неправильные варианты вызова процедур/функций (я про пункт "7. Проблемы с системой типов языка")
Цитата
я так понял, что во многом Ада даёт "защиту от дурака"
Это не совсем "защита от дурака". Это надежность программ прежде всего.
Кстати, очень многие вещи остались "за кадром": встроенная в язык работа с подзадачами/потоками (с 1983 года !!! Причем средствами языка, нет никакой зависимости ни от ОС, ни от используемого компилятора) и межпоточное взаимодействие (аналогично, средствами языка), и защищенные типы (попробуй написать потокобезопасный контейнер на Паскале, например, чтоб с ним можно было безопасно работать из десятка потоков)... И так пост большой получился... Если кому интересно - расскажу.
-TarasBer-
5.01.2011 0:09
> Что бросается в глаза? "Многословность".
Насколько я знаю, для закрытия блока надо тоже его назвать. По-моему, это очень сильно избавляет от поиска пропущенного енда (вызывающиего ошибку компиляции под конец модуля) по всему коду, то есть плюс.
> Описание переменных по мере необходимости.
Это прикольно, но процедуры на более чем 100 строк считаются дурным тоном. А для коротких процедур завести переменную в начале не обломно.
> Циклы For.
+
> Проверка логических условий.
Все уже сто лет привыкли, что полной проверки нет и пользуются этим. На крайняк можно написать if a then if b...
> Оператор Goto
+
> Дефолтные параметры процедур/функций.
+
> Инициализация массивов.
+
> Еще немного о массивах.
> (чем не "сборка мусора"?)
Надеюсь, там не сборка, а детерминированное удаление при выходе из зоны видимости? Другой автоматизации работы с памятью я не признаю.
> Параметры подпрограмм.
Не вкурил. Есть же директива const.
> Дженерики.
То есть можно делать приколы из С++, в котором шаблоны - функциональный язык, выполняющийся во время компиляции? Это интересно.
> Системы счисления
+
> Смешивание "=" и "==".
За это надо построить машину времени, вернуться в прошлое и убить Ритчи. И эта зараза во всех новых языках!!!
> Поговорим о перечислениях...
А что, перечисления Си даже в отладчике видны как числа? Я так не смогу, я привык, что в отладчике вижу нормальные имена.
> гибкие селекторы - можно сделать и так:
Да, тут у Си полный облом.
> Ошибка компиляции: несоответствие типов параметров
Это правильно, но принудительной ретипизацией это можно обойти?
> В Аде каждый раз, когда происходит переполнение выбрасывается исключение (на всех поддерживаемых платформах). То же смое касается и попытки деления на 0, и выхода за пределы массива, ну, и так далее...
Не понял, в код вставляются проверки выхода за диапазон? Это отрубаемо, надеюсь?
> только непонятно, в чем заключалось условие, в успешном присваивании?
Нет. В том, что результат присваивания не ноль.
> var a:integer; > m:array [1..a] of byte;
И как ты это себе представляешь? Не, ну если массив идёт в самом конце описания, то это ещё можно сделать теоретически. А если нет? То что делать с участком памяти, идущим за массивом, когда массив захотелось удлинить?
-TarasBer-
5.01.2011 0:17
А вот что меня напрягает в синтаксисе Паскаля (и у Ады тоже, кажется, есть этот минус), так это то, что функции, не принимающие параметров, можно записывать без пустых скобочек. Где-то была тема, в которой три человека очень долго думали, что в данном контексте значила pointer(a) - вызов метода или указатель на метод? Нужен был указатель, но почему он не вызовется, сказать наверняка было невозможно. Да и вообще, это нарушает логичность.
volvo
5.01.2011 0:43
Цитата
Насколько я знаю, для закрытия блока надо тоже его назвать.
Это настраиваемо. Мой компилятор с этим справляется сам, посему у меня отключено. А вообще - да, окончательную проверку на Styling программа не пройдет, пока все отступы не будут выровнены, все блоки не будут названы, и все подпрограммы не будут размещены в алфавитном порядке с предварительным описанием в файле спецификаций (некий аналог Interface-части юнитов Паскаля. Кстати, по той же причине - облегчение поиска в случае необходимости).
Цитата
А для коротких процедур завести переменную в начале не обломно.
Ниже я написал один из способов применения. Это удобно...
Цитата
Все уже сто лет привыкли, что полной проверки нет и пользуются этим.
А если мне НАДО полную проверку? Ада-решение: вместо "and then" пишется просто "and", вместо "or else" - просто "or"... Паскаль заставляет меня опять заморачиваться с директивами компиляции, остальные языки - тоже...
Цитата
Надеюсь, там не сборка, а детерминированное удаление при выходе из зоны видимости? Другой автоматизации работы с памятью я не признаю.
Я тоже не терплю языков, которые навязывают мне свой GC. По этой причине я не стал изучать ничего из .NET-а, по этой же причине не ушел на Оберон... Там именно удаление при выходе из области видимости... Сборшик мусора Стандартом Ады не запрещен, но и не навязывается. Есть - используй, нет - не используй...
Цитата
А что, перечисления Си даже в отладчике видны как числа?
Отладчик добавляет к числу еще и идентификатор. Но все равно, неявное приведение к типу int делает свое черное дело при работе программы...
Цитата
Не понял, в код вставляются проверки выхода за диапазон? Это отрубаемо, надеюсь?
Да, для этого надо пересобрать программу с другими ключами.
Добавлено через 9 мин.
Цитата
Нужен был указатель, но почему он не вызовется, сказать наверняка было невозможно.
У Ады совершенно нет этой проблемы (по крайней мере, попадать в такое положение, при котором возникала бы неоднозначность мне не приходилось). Если нужен указатель на метод - P'Access, нужен вызов метода - просто P... В Паскале - помню, было... А пустые скобки меня в Дельфи/FPC напрягают, например.
TarasBer
5.01.2011 2:22
> Ниже я написал один из способов применения. Это удобно...
С одной стороны, удобно, с другой, не бьёт по рукам любителей простыней.
> А если мне НАДО полную проверку?
Зачем? Ради побочных эффектов вычисления операндов? Тогда заранее в отдельные переменные записываешь результаты вычислений операндов. По-моему это не так часто происходит, чтобы заводить отдельно оператор "и-с-полной-проверкой" и оператор "и-с-неполной-проверкой". По крайней мере я ни разу не ощутил потребность в таком операторе.
> Там именно удаление при выходе из области видимости...
И можно создавать свои объекты, которые в автовызываемом деструкторе делают освобождения ресурсов, закрытия файлов итд? А ГЦ недетерминирован, ставит крест на приложениях реального времени, всё равно в кривых руках память течёт (да), а в прямых, если в языке есть автовызов деструктора, нафиг он нужен? Стандартный счётчик ссылок не работает только в хитрых структурах, двусвязных списках, например, ну так для них объектную оболочку можно сделать, чтобы все опасные операции внутри реализации класса были зашиты, а оболочка это сама всё вычистит при выходе из зоны видимости.
> Да, для этого надо пересобрать программу с другими ключами.
Тогда нормально. Хошь - компилируй в программу с полным контролем и безопасностью, хошь - на скорость дави.
> У Ады совершенно нет этой проблемы
Ну надеюсь. Просто пустые скобки - это как сигнал, что типа это может иметь побочные эффеты, если что, надо кешировать.
И можно создавать свои объекты, которые в автовызываемом деструкторе делают освобождения ресурсов, закрытия файлов итд?
Угу.
-- В отдельном пакете описываем объект: type MyObject is new Controlled with record ... -- Тут все, что мне нужно end record;
procedure Initialize (Obj : in out MyObject); procedure Finalize (Obj : in out MyObject);
Для Controlled-типов процедуры Initialize и Finalize (конструктор/деструктор, можно и так) вызываются автоматически. Все, что мне останется сделать:
declare Obj : MyObject; -- Тут вызовется Initialize begin -- Работаем с объектом end; -- Тут же, при выходе из области видимости, вызовется Finalize
Если же мне захочется самому вызывать конструктор/деструктор - не надо наследоваться от Controlled...
TarasBer
5.01.2011 4:03
> Для Controlled-типов процедуры Initialize и Finalize (конструктор/деструктор, можно и так) вызываются автоматически.
Всё, хочу перейти на Аду. Есть какие-нибудь бесплатные среды под Вин32? Мне достаточно редактора кода и заголовков от ВинАПИ, формошлёпалку не надо.
Добавлено через 9 мин. А, я что ещё вспомнил. Могут ли out-параметры быть по умолчанию? Мне не удалось в Дельфи написать var s: string = nil^, пришлось вспомнить, что передача по ссылке - это неявная передача указателя и написать s: PString = nil.
volvo
5.01.2011 4:16
Цитата
Есть какие-нибудь бесплатные среды под Вин32?
Есть. Без формошлепалки, IDE для Win/Lin со всеми необходимыми пакетами можно взять здесь (просто выбираешь Free sortware or Academic Development, можешь даже не указывать мыло, и собираешь для себя пакет скачки. Обязательно качать Gnat GPL и Win32Ada, это как раз то, что ты описал - заголовки WinAPI и компилятор со всеми прибамбасами. Я для себя еще брал QtAda, но это - для любителя особых извращений...)
Добавлено через 8 мин.
Цитата
Могут ли out-параметры быть по умолчанию?
Нет, только In-параметры. Даже In Out не могут. А оно надо?
TarasBer
5.01.2011 4:28
> Нет, только In-параметры. Даже In Out не могут. А оно надо?
Ну мне как-то понадобилось, чтобы функция когда надо - возвращала значение в некий параметр, а когда не надо - не возвращала, ну то есть иногда он мне не интересен, а заводить спец переменную ради возвращения того, что мне не надо, некрасиво. Пришлось извращаться через передачу указателя.
volvo
5.01.2011 5:20
Цитата
а заводить спец переменную ради возвращения того, что мне не надо, некрасиво.
Я посмотрю, что ты скажешь, когда Ада заставит тебя вместо ReleaseDC (hwnd, My_DC); написать
, ибо функция должна в любом случае значение вернуть (а еще интереснее - если подряд вызываются три функции, одна возвращает Win32.BOOL, вторая - Win32.INT, а третья - Win32.LONG. Вот и опишешь три переменных: i_Result, b_Result и L_Result, хотя они тебе тоже не особо и сдались...). Это тебе не С, где на такие вещи можно просто не обращать внимание. Это - Ада. Не сделал - не откомпилировал...
Все Сиш-ные и Дельфийские замашки, основанные на хаках и бесконтрольном приведении типов придется забыть. Ибо Ада тебе даже не гарантирует, что матрица хранится по строкам. Она может храниться и по столбцам, как в Фортране...
Lapp
5.01.2011 17:20
volvo, спасибо за это эссе, +1
Я встряну мимоходом.
Цитата(volvo @ 4.01.2011 20:43)
А если мне НАДО полную проверку? Ада-решение: вместо "and then" пишется просто "and", вместо "or else" - просто "or"... Паскаль заставляет меня опять заморачиваться с директивами компиляции, остальные языки - тоже...
Я уже как-то говорил об этом.. За всю свою практику я не встретил ни одного случая, где это было "НАДО". Все примеры на эту тему как правило сродни чесанию правой пяткой за левым ухом. Так что я для себя сейчас считаю, что если мне это вдруг "НАДО" - значит, что-то не так )). Логика - штука хитрая. Красота выражения говорит о его правильности обычно. Так что лично я не буду возражать, если мне однажды все-таки придется "заморочиться с директивами"..
Цитата(Unconnected @ 4.01.2011 15:05)
Фухх, осилил) С многим сам сталкивался, вот например кажется такой код в С++
int k; if (k=5) {...}
, и это компилировалось, насколько помню..
А вот это как раз очень жизненно. С одной стороны, этой ошибке подвержены практически все, кто переходит с Паскаля на Си. А с другой,
Цитата
только непонятно, в чем заключалось условие, в успешном присваивании?
- такая конструкция очень удобна и часто применяется. Значит она следующее: присваиваем k некоторое значение (конечно, там стоит обычно выражение из переменных или функций), и если оно ненулевое, то.. Забавно, что паскалисту (я говорю про начинающих, а не зрелых, которые осознают потребность такого даже в Паскале) такая конструкция совершенно чужда, он просто не мыслит в таких категориях )).
Offtop: Это сообщение не увидело бы свет, если бы FF после крэша машины (блин, это меня уже напрягает, надо что-то делать) не восстановил практически полный его текст. За что ему спасибо!
подправил, была опечатка..
TarasBer
5.01.2011 19:29
> Все Сиш-ные и Дельфийские замашки, основанные на хаках и бесконтрольном приведении типов придется забыть.
Э, а как же прямая отрисовка в буфер? У меня есть только указатель на начало пиксельного блока, ширина и высота картинки. Вся отрисовка держится на бесконтрольной ретипизации.
> Забавно, что паскалисту (я говорю про начинающих, а не зрелых, которые осознают потребность такого даже в Паскале) такая конструкция совершенно чужда, он просто не мыслит в таких категориях )).
А такая?
if (i := 5) <> 0 then begin end;
TarasBer
5.01.2011 19:59
> for i in 1 .. 10 loop -- i вообще не описывается
А если написать так? for i in 1 .. L.Count - 1 loop то, надеюсь, будет использован знаковый тип? Вспомнился прикол, как некоторые брали word и материли "глючную дельфу", которая почему-то для пустого списка пыталась перебирать индексы от 0 до 65535.
volvo
5.01.2011 21:08
Цитата
Вся отрисовка держится на бесконтрольной ретипизации.
Для крайних случаев (когда без этого уже совсем никак не обойтись) есть модуль Ada.Unchecked_Conversion, который позволяет привести любой тип к любому другому. Но слишком часто прибегать к его услугам (в смысле строить программу так, чтобы были сплошные преобразования типов, потому что "я сам знаю, как мне делать, почему меня компилятор ограничивает?") - это моветон.
Цитата
А если написать так? for i in 1 .. L.Count - 1 loop то, надеюсь, будет использован знаковый тип?
> Я посмотрю, что ты скажешь, когда Ада заставит тебя вместо
Мда, я представил для SendMessage... Ну для самый частых функций ещё обёртку можно сделать, проглатывающую возвращаемое значение. Кстати, инлайн-функции там есть?
> Но слишком часто прибегать к его услугам (в смысле строить программу так, чтобы были сплошные преобразования типов, потому что "я сам знаю, как мне делать, почему меня компилятор ограничивает?") - это моветон.
А мне, кроме графического модуля, нигде и не надо. В Д7 я ещё делал наглые преобразования к типу, с которым работает некоторая структура, но это от отсутствия генериков.
> Еще сомневаешься?
Тогда нормально. А вместо диапазона писать название перечислимого типа можно? for i in TMyType do... А то так запарило в одном месте для 3 разных типов писать
var imt: TMyType; ... for imt := Low(TMyType) to High(TMyType) do...
Что ещё не хватало в Дельфи. А, контроль вариантных полей во время выполнения (во время компиляции, увы, никак).
type TMyRec = record case Selector: byte of 0: b: array [0 .. 9] of byte; 1: w: array [0 .. 4] of word; end; end; ... r.Selector := 0; r.w[0] := 1; // чтобы тут выдавало ошибку выполнения (потому что поле относится к другому значению селектора), // разумеется, при включённых флагах компиляции
volvo
6.01.2011 1:29
Цитата
А вместо диапазона писать название перечислимого типа можно?
Легко... Нажмите для просмотра прикрепленного файла Будет перебираться весь тип, от самого первого до самого последнего значения, и всего делов. Обрати внимание, для того чтобы напечатать идентификатор элемента, достаточно сделать MyType'Image. Это тоже встроено в язык, не нужны никакие Дельфийские шаманства с RTTI.
Цитата
А, контроль вариантных полей во время выполнения (во время компиляции, увы, никак).
(правда тут у тебя небольшой облом - селектор должен задаваться один раз, при инициализации переменной. Менять его ты не имеешь права. Задавать переменную без селектора - тоже). Но как доп. средство для контроля - пойдет.
TarasBer
6.01.2011 1:51
> Обрати внимание, для того чтобы напечатать идентификатор элемента, достаточно сделать MyType'Image.
Да, такую фичу я тоже хотел (типа чтобы Str(mt, S) писало в S имя mt), но так, в качестве баловства, серьёзного применения ей пока не вижу. Image - это функция, возвращающая строку?
> правда тут у тебя небольшой облом - селектор должен задаваться один раз, при инициализации переменной
Это я на стадии компиляции должен указывать значение селектора? Тогда что вообще дают такие вариантные записи? Или всё это не обязательно?
volvo
6.01.2011 2:47
Ну, максимум что можно предложить - это описать запись как "нелимитированную", то есть дать селектору значение по умолчанию, что позволит описывать экземпляр структуры без начального значения селектора, и, следовательно, разрешит менять селектор в рантайме. Только менять его можно будет только вот таким образом: Нажмите для просмотра прикрепленного файла (то есть, в агрегате должны присутствовать все поля, связанные с данным селектором. Как видишь, это очень просто делается. А потом - уже меняй любое нужное тебе значение. Если то, что ты меняешь недоступно при установленном в настоящий момент селекторе, вылетит CONSTRAINT_ERROR).
TarasBer
6.01.2011 2:57
> и, следовательно, разрешит менять селектор в рантайме
Во, тогда нормально.
> Если то, что ты меняешь недоступно при установленном в настоящий момент селекторе, вылетит CONSTRAINT_ERROR
Да, этого я и хотел (но опять же, как отключаемую опцию компилятора).
Я понял, почему Image для перечислимых типов для меня бесполезен. Типы я перечисляю в коде на английском, сообщения для пользователя пишу на русском (возможность писать идентификаторы на русском не предлагать, запарюсь переключаться). Да, будь я носителем языка с латинницей, оценил бы эту возможность по достоинству.
Система типов интересная (я про Unsigned_8 итд), намного логичнее, чем long long long int и прочие нелепые названия.
volvo
6.01.2011 3:56
Цитата
этого я и хотел (но опять же, как отключаемую опцию компилятора).
Обрати внимание на вывод внизу экрана, ошибки уже нет. Достаточно было просто поставить одну-единственную галочку...
Из недавнего - просто вспомнилось (к вопросу о безопасности и контроле программиста над происходящим в программе)...
Все помнят такую особенности Дельфи / Object Pascal, как передачу объекта класса по ссылке, и к чему это приводит, да? Иллюстрация:
// есть вот такой класс type T = class value : Integer; constructor Create(AValue : Integer); procedure Print; end;
constructor T.Create (AValue : Integer); begin Inherited Create; Value := AValue; end;
procedure T.Print; begin writeln(value); end;
// и вот такая процедура (полиморфная) procedure DoSomething (Const Obj : T); begin Obj.Print; Obj.value := 10; // <--- Изменение объекта end;
// Используется вот так var Obj : T; begin Obj := T.Create(5); Obj.Print;
DoSomething(Obj);
Obj.Print; Obj.Free; end.
Как вы думаете, что в данном случае означает Const? Неужели - то, что объкет класса нельзя изменить, и Дельфи мне выдаст ошибку компиляции или хотя бы аварийно завершит программу при попытке изменения объекта Obj? Да ничего подобного. Легко изменяется любое поле объекта, хоть напрямую, хоть через методы/свойства.
Это по меньшей мере нелогично. Если я передаю объект в процедуру с таким прототипом: procedure DoSomething (Const Obj : T); , я ожидаю, что объект не будет изменен. Дельфи мне этого обеспечить не может. В отличие от С++. Что делается в Аде? Тут как раз все в порядке:
(описание класса)
-- Specification file (ADS) package MyObj is
type Root is tagged record A : Integer; end record;
procedure Print (Obj : Root); procedure Set_Value (Obj : in out Root; Value : Integer);
end MyObj;
-- Body file (ADB) with ada.Text_IO; use ada.Text_IO;
package body MyObj is
procedure Print (Obj : Root) is begin Put_Line (Integer'Image (Obj.A)); end Print;
procedure Set_Value (Obj : in out Root; Value : Integer) is begin Obj.A := Value; end Set_Value;
end MyObj;
(основная программа)
procedure Polymorphic (Obj : in MyObj.Root) is begin Obj.Print; -- Да пожалуйста, объект не будет изменяться Obj.Set_Value (3); -- А вот тут - стоп !!! Это изменит объект end Polymorphic;
, компиляция прекратится при попытке вызвать метод, модифицирующий Obj. Так что вот вам еще одна ступень защиты. Если же параметр Obj описать как in out, то есть, изменяемый - то и Set_Value прекрасно отработает.
TarasBer
20.01.2011 23:37
Эээ, а кто может обеспечить безопасность данных, передаваемых по указателю, даже если он передан с модификатором const? Что касается классов, то в Дельфи класс - это указатель. А в С++ просто классы сделаны как положено (да, мне С++ная концепция ООП нравится куда больше), а не в виде указателей, которые ещё и надо РУКАМИ удалять. В Дельфи, чтобы передать содержимое, а не указатель, надо пользоваться не class, а object. Короче, ООП от Борланда мне (из-за этого в том числе) стало казаться настолько неестественным, что я стал жёстко структурить, выдавая иногда перлы типа http://govnokod.ru/4249 или http://govnokod.ru/5261 (да, именно тут мне захотелось контроля обращения к вариантному полю в зависимости от знаения селектора). Зато, я считаю, только после таких вещей начинаешь по-настоящему понимать смысл ООП, а не так, что дяди сказали, что ООП типо крута, а сам сидишь и лепишь его везде.
volvo
21.01.2011 0:55
Цитата
Эээ, а кто может обеспечить безопасность данных, передаваемых по указателю, даже если он передан с модификатором const?
Хм. Значит, надо было отменять модификатор const, или доделывать его, чтоб он работал со всеми сущностями, а не отсылать программиста разбираться, является ли классом строка, и почему на нее спецификатор действует, а на другие классы - нет.
Тем более, что это возможно, как оказалось. В Аде объекты тоже не передаются по значению, однако если я заказал доступ только по чтению - то он таким и будет, и мне не надо волноваться, что мой объект будет изменен в результате.
TarasBer
10.02.2011 19:57
Забавно, но самый страшный баг был написан именно на Аде:
Блин... Это не проблема Ады. Есть посекундный анализ этой катастрофы, который показал, в чем на самом деле была проблема. Показать? Кому-то просто выгодно муссировать слухи, о том, что именно язык МО США дал такой сбой...
TarasBer
10.02.2011 21:04
> Есть посекундный анализ этой катастрофы, который показал, в чем на самом деле была проблема. Показать?
Да.
> Кому-то просто выгодно муссировать слухи, о том, что именно язык МО США дал такой сбой...
Во всех официальных источниках обвиняется именно программное обеспечение. Хотя я понимаю, что С++ бы туда за пушечный выстрел не допустили и с ним бы ракета, скорее всего, даже не взлетела бы.
volvo
10.02.2011 21:20
Цитата
Во всех официальных источниках обвиняется именно программное обеспечение.
Было обосновано, что в случае события отмены старта период в 50 сек. после H0-9 будет достаточным для того, чтобы наземное оборудование смогло восстановить полный контроль за Инерциальной Платформой без потери информации – за это время Платформа прекратит начавшееся было перемещение, а соответствующий программный модуль всю информацию о ее состоянии зафиксирует, что поможет оперативно возвратить ее в исходное положение (напомним, что все это в случае, когда ракета продолжает находиться на месте старта). И действительно, однажды, в 1989 г., при старте под номером 33 ракеты Ariane 4, эта особенность была с успехом задействована.
Однако, Ariane 5, в отличие от предыдущей модели, имел уже принципиально другую дисциплину выполнения предполетных действий – настолько другую, что работа рокового программного модуля после времени старта вообще не имела смысла. Однако, модуль повторно использовался без каких-либо модификаций – видимо из-за нежелания изменять программный код, который успешно работает.
<...>
Расследование показало, что в данном программном модуле присутствовало целых семь переменных, вовлеченных в операции преобразования типов. Оказалось, что разработчики проводили анализ всех операций, способных потенциально генерировать исключение, на уязвимость. И это было их вполне сознательным решением добавить надлежащую защиту к четырем переменным, а три – включая BH – оставить незащищенными. Основанием для такого решения была уверенность в том, что для этих трех переменных возникновение ситуации переполнения невозможно в принципе. Уверенность эта была подкреплена расчетами, показывающими, что ожидаемый диапазон физических полетных параметров, на основании которых определяются величины упомянутых переменных, таков, что к нежелательной ситуации привести не может. И это было верно – но для траектории, рассчитанной для модели Ariane 4. А ракета нового поколения Ariane 5 стартовала по совсем другой траектории, для которой никаких оценок не выполнялось. Между тем она (вкупе с высоким начальным ускорением) была такова, что "горизонтальная скорость" превзошла расчетную (для Ariane 4) более чем в пять раз.
Но почему же не была (пусть в порядке перестраховки) обеспечена защита для всех семи, включая BH, переменных? Оказывается, для компьютера IRS была продекларирована максимальная величина рабочей нагрузки в 80%, и поэтому разработчики должны были искать пути снижения излишних вычислительных издержек. Вот они и ослабили защиту там, где теоретически нежелательной ситуации возникнуть не могло. Когда же она возникла, то вступил в действие такой механизм обработки исключительной ситуации, который оказался совершенно неадекватным.
Этот механизм предусматривал следующие три основных действия. Прежде всего, информация о возникновении нештатной ситуации должна быть передана по шине на бортовой компьютер OBC; параллельно она – вместе со всем контекстом – записывалась в перепрограммируемую память EEPROM (которую во время расследования удалось восстановить и прочесть ее содержимое), и наконец, работа процессора IRS должна была аварийно завершиться. Последнее действие и оказалось фатальным – именно оно, случившееся в ситуации, которая на самом деле была нормальной (несмотря на сгенерированное из-за незащищенного переполнения программное исключение), и привело к катастрофе.
Итого: недостаток вычислительной мощности, из-за которого программисты осмысленно снимают защиту (что в результате приводит к сбою) - это проблема языка? Нет. То, что траектория Ариан-5 не оценивалась во всей серьезностью, а кто-то положился на то, что все будет так же, как и в Ариан-4 - это что, проблема Ады? Нет. В чем же тогда обвиняют язык? В том, что кто-то неизвестно почему не мог усилить процессоры? В том, что не было выделено средств на тестирование баллистических характеристик? В том, что кто-то вообще не дал программистам просмотреть код, чтоб не платить им, а заявил, что будет использоваться прекрасно показавшая себя в прошлом проекте часть? Почему Ада виновата?
TarasBer
10.02.2011 21:28
> Итого: недостаток вычислительной мощности, из-за которого программисты осмысленно снимают защиту (что в результате приводит к сбою) - это проблема языка?
Это может быть проблемой компилятора, не умеющего эффективно оптимизировать код (не знаю, насколько это относится к Аде).
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.