Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Языки программирования. Практический сравнитель...doc
Скачиваний:
54
Добавлен:
09.09.2019
Размер:
2.68 Mб
Скачать

5.3. Массивы и контроль соответствия типов

Возможно, наиболее общая причина труднообнаруживаемых ошибок — это индексация, которая выходит за границы массива:

inta[10],

C

for(i = 0;

i<= 10; i

a[i] = 2*i;

Цикл будет выполнен и для i = 10, но последним элементом массива является а[9].

Причина распространенности этого типа ошибки в том, что индексные выражения могут быть произвольными, хотя допустимы только индексы, по­падающие в диапазон, заданный в объявлении массива. Самая простая ошиб­ка может привести к тому, что индекс получит значение, которое выходит за этот диапазон. Серьезность возникающей ошибки в том, что присваивание a[i] (если i выходит за допустимый диапазон) вызывает изменение некоторой случайной ячейки памяти, возможно, даже в области операционной системы. Даже если аппаратная защита допускает изменение данных только в области вашей собственной программы, ошибку будет трудно найти, так как она про­явится в другом месте, а именно в командах, которые используют изменен­ную память.

Рассмотрим случай, когда числовая ошибка заставляет переменную speed получить значение 20 вместо 30:

C

intx=10,y=50;

speed = (х+у)/3; /*Вычислить среднее! */

Проявлением ошибки является неправильное значение speed, и причина (де­ление на 3 вместо 2) находится здесь же, в команде, которая вычисляет speed. Это проявление непосредственно связано с ошибкой и, используя контроль­ные точки или точки наблюдения, можно быстро локализовать ошибку. В следующем примере:

inta[10];

C

int speed;

for(i = 0;i<= 10; i ++)

a[i] = 2*j;

переменная speed является жертвой того факта, что она была чисто случайно объявлена как раз после а и, таким образом, была изменена совершенно по­сторонней командой. Вы можете днями прослеживать вычисление speed и не найти ошибку.

Решение подобных проблем состоит в проверке операции индексации над массивами с тем, чтобы гарантировать соблюдение границ. Любая попытка превысить границы массива рассматривается как нарушение контроля соот­ветствия типов. Впервые проверка индексов была предложена в языке Pascal:

pascal


type A_Type = array[0..9] of Integer;

A: A_Type;

A[10]:=20; (*Ошибка*)

При контроле соответствия типов ошибка обнаруживается сразу же, на своем месте, а не после того, как она «затерла» некоторую «постороннюю» память; целый класс серьезных ошибок исчезает из программ. Точнее, такие ошибки становятся ошибками этапа компиляции, а не ошибками этапа выполнения программы.

Конечно, ничего не дается просто так, и существуют две проблемы конт­роля соответствия типов для массивов. Первая — увеличение времени выпол­нения, которое является ценой проверок (мы обсудим это в одном из следую­щих разделов). Вторая проблема — это противоречие между способом, кото­рым мы работаем с массивами, и способом работы контроля соответствия ти­пов. Рассмотрим следующий пример:

pascal

typeA_Type = array[0..9]of Real; (* Типы массивов *)

type B_Type= array[0..8] of Real;

А: А_Туре: (* Переменные-массивы *)

В: В_Туре;

procedure Sort(var P: А_Туре); (* Параметр-массив *)

sort(A); (* Правильно*) sort(B); (* Ошибка! *)

Два объявления типов определяют два различных типа. Тип фактического па­раметра процедуры должен соответствовать типу формального параметра, по­этому кажется, что необходимы две разные процедуры Sort, каждая для свое­го типа. Это не соответствует нашему интуитивному понятию массива и опе­раций над массивом, потому что при тщательном программировании проце­дур, аналогичных Sort, их делают не зависящими от числа элементов в масси­ве; границы массива должны быть просто дополнительными параметрами. Обратите внимание, что эта проблема не возникает в языках Fortran или С по­тому, что в них нет параметров-массивов! Они просто передают адрес начала массива, а программист отвечает за правильное определение и использование границ массива.

В языке Ada изящно решена эта проблема. Тип массива в Ada определяется исключительно сигнатурой, т. е. типом индекса и типом элемента. Такой тип называется типом массива без ограничений. Чтобы фактически объявить массив, необходимо добавить к типу ограничение индекса:

Ada


type A_Type is array(lnteger range о) of Float;

-- Объявление типа массива без ограничений

А: А_Туре(0..9); — Массив с ограничением индекса

В: А_Туре(0..8); — Массив с ограничением индекса

Сигнатура А_Туре — одномерный массив с индексами типа integer и компо­нентами типа Float; границы индексов не являются частью сигнатуры.

Как и в языке Pascal, операции индексации полностью контролируются:

Ada

А(9) := 20.5; -- Правильно, индекс изменяется в пределах 0..9

В(9) := 20.5; -- Ошибка, индекс изменяется в пределах 0..8

Важность неограниченных массивов становится очевидной, когда мы рассматриваем параметры процедуры. Так как тип (неограниченного) мас­сива-параметра определяется только сигнатурой, мы можем вызывать проце­дуру с любым фактическим параметром этого типа независимо от индексного ограничения:

Ada

procedure Sort(P: in out A_Type);

— Тип параметра: неограниченный массив

Sort(A); -- Типом А является А_Туре

Sort(B); -- Типом В также является А_Туре

Теперь возникает вопрос: как процедура Sort может получить доступ к гра­ницам массива? В языке Pascal границы были частью типа и таким образом были известны внутри процедуры. В языке Ada ограничения фактического параметра-массива автоматически передаются процедуре во время выполне­ния и могут быть получены через функции, называемые атрибутами. Если А произвольный массив, то:

• A'First — индекс первого элемента А.

• A'Last — индекс последнего элемента А.

• A'Length — число элементов в А.

• A'Range — эквивалент A'First.. A'Last.

Например:

Ada

procedure Sort(P: in out A_Type) is begin

for I in P'Range loop

for J in 1+1 .. P'Lastloop

end Sort;

Использование атрибутов массива позволяет программисту писать чрезвы­чайно устойчивое к изменениям программное обеспечение: любое изменение границ массива автоматически отражается в атрибутах.

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