ТЕОРИЯ ВЫЧИСЛИТЕЛЬНЫХ СИСТЕМ. Теория вычислительных процессов
Скачать 2.17 Mb.
|
DO: do B1 → S1 П B2 → S2 ... П Bn → Snod, где n 0, Bi → Si- охраняемые команды. Выполняется оператор следующим образом. Пока возможно выбирается охрана Bi со значением истина, и выполняется соответствующий оператор Si. Как только все охраны будут иметь значение ложь, выполнение DO завершится. Выбор охраны со значением истина и выполнение соответствующего оператора называется выполнением шага цикла. Если истинными являются несколько охран, то выбирается любая из них. Следовательно, оператор DO эквивалентен оператору do BB→ if B2 → S1 П B2 → S2 ... П Bn → Snfi od или do BB → IF od, где BB - дизъюнкция охран, IF - оператор выбора. Пример 2.5. Алгоритм Евклида. Вариант 1. задать (N, M); if N > 0 AND M > 0 → n, m := N, M; do n ≠ m → if n > m → n := n – m П m > n → m := m – n fi od; выдать (n) fi. Вариант 2. задать (N, M); if N > 0 AND M > 0 → n, m := N, M; do n > m → n := n – m П m > n → m := m – n od; выдать (n) fi. Дадим формальное определение слабейшего предусловия для оператора цикла DO. Пусть предикат H0(R) определяет множество состояний, в которых выполнение DO завершается за 0 шагов (в этом случае все охраны с самого начала ложны, после завершения R имеет значение истина): H0(R) = NOT BB AND R. Другими словами, требуется, чтобы оператор цикла DO завершил работу, не производя выборки охраняемой команды, что гарантируется первым конъюнктивным членом предиката H0(R): NOT BB = T. При этом истинность R до выполнения DO является необходимым условием для истинности R после выполнения DO. Определим предикат Hk(R) как множество состояний, в которых выполнение DO заканчивается за k шагов при значении R истина (Hk(R) будет определяться через Hk-1(R)): Hk(R) = H0(R) OR wp(IF, Hk-1(R)), k > 0 → wp(DO, R) = ( k: k 0: Hk(R)). Это значит, что должно существовать такое значение k, что потребуется не более чем k шагов, для обеспечения завершения работы в конечном состоянии, удовлетворяющем постусловию R. Определение DO. Если предикаты Hk(R) задаются в виде Hk(R) = NOT B AND R, k= 0, Hk(R) = wp(IF, Hk-1(R)), k > 0, → wp(DO, R)=( k: k 0: Hk(R)). Основная теорема для оператора цикла. Пусть оператор выбора IF и предикат P таковы, что для всех состояний справедливо (P AND BB) => wp(IF, R).(2.3) Тогда для оператора цикла справедливо: (P AND wp(DO, T)) => wp(DO, P AND NOT BB). Эта теорема известна как основная теорема инвариантности для цикла. Предикат Р, истинный перед выполнением и после выполнения каждого шага цикла, называется инвариантным отношением или просто инвариантом цикла. В математике термин «инвариантный» означает не изменяющийся под воздействием совокупности рассматриваемых математических операций. Поясним смысл теоремы. Условие (2.3) означает, что если предикат P первоначально истинен и одна из охраняемых команд выбирается для выполнения, то после ее выполнения P сохранит значение истинности. После завершения оператора, когда ни одна из охран не является истиной, будем иметь: P AND NOT BB. Работа завершится правильно, если условие wp(DO, T) справедливо и до выполнения DO. Так как любое состояние удовлетворяет T, то wp(DO, T) является слабейшим предусловием для начального состояния такого, что запуск оператора цикла DO приведет к правильно завершаемой работе. Доказательство. Из определения DO следует: H0(T) = NOT BB AND T, k = 0; Hk(T) = wp(IF, Hk-1(T)), k > 0, → wp(DO, T) = ( k: k 0: Hk(T)); H0(P AND NOT BB) = P AND NOT BB; Hk(P AND NOT BB) = wp(IF, Hk-1(P AND NOT BB), → wp(DO, P AND NOT BB) = = ( k 0: Hk(P AND NOT BB)). С помощью метода математической индукции можно доказать, что из условия (2.3) следует (P AND Hk(T)) => Hk(P AND NOT BB), k 0. Тогда имеем P AND wp(DO, T) = ( k: k 0: P AND Hk(T)) => ( k: k 0: Hk(P AND NOT BB)) = = wp(DO, P AND NOT BB). Следовательно, (P AND wp(DO, T)) => wp(DO, P AND NOT BB). Чтобы использовать аксиоматическую семантику с данным языком программирования для доказательства правильности программ или для описания формальной семантики, для каждого вида операторов языка должны быть определены аксиомы или правила логического вывода. С ними мы познакомимся в п. 2.3 «Верификация программ». В аксиоматической семантике система (2.1) интерпретируется как набор аксиом в рамках некоторой формальной логической системы, в которой есть правила вывода и/или интерпретации определяемых объектов. Для интерпретации системы функций (2.1) вводится понятие аксиоматического описания <S, A> - логически связанной пары понятий: S - сигнатура используемых в системе символов функций f1, f2, ..., fm и символов констант (нульместных функциональных символов) c1, c2, ..., cl, а A - набор аксиом, представленный системой. Предполагается, что каждая переменная xi, i = 1, ..., k, и каждая константа ci, i = 1, ..., l, используемая в A, принадлежит к какому-либо из типов данных t1, t2, ..., tr, а каждый символ fj, j = 1, ..., m, представляет функцию, типа: ti1 ti2t... ik ti0. Такое аксиоматическое описание получит конкретную интерпретацию, если будут заданы конкретные типы данных ti = ti', i = 1, ..., r, и конкретные значения констант ci = ci', i = 1, ..., l. К. Хоaр построил аксиоматическое определение набора типов данных, которые потом Н. Вирт использовал при создании языка Паскаль. Пример 2.6. Рассмотрим систему равенств: УДАЛИТЬ(ДОБАВИТЬ(m,d)) = m, ВЕРХ(ДОБАВИТЬ(m,d)) = d, УДАЛИТЬ(ПУСТ) = ПУСТ, ВЕРХ(ПУСТ) = ДНО, где УДАЛИТЬ, ДОБАВИТЬ, ВЕРХ - символы функций, а ПУСТ и ДНО - символы констант, образующие сигнатуру этой системы. Пусть D, D1 и М - некоторые типы данных, такие, что m M, d D, ПУСТ M, ДНО D1, а функциональные символы представляют функции следующих типов: УДАЛИТЬ: M M, ДОБАВИТЬ:M, D M, ВЕРХ:M D1. Данная сигнатура вместе с указанной системой равенств, рассматриваемой как набор аксиом, образует аксиоматическое описание абстрактного типа данных, называемого магазином. Зададим интерпретацию символов ее сигнатуры: D - множество значений, которые являются элементами магазина, M - множество состояний магазина, M = {d1,d2, ... ,dn: di D, i= 1, ..., n, n 0}, D1=D {ДНО}, ПУСТ={}, а ДНО - особое значение (зависящее от реализации магазина), не принадлежащее D. Тогда указанный набор аксиом определяют свойства магазина. При определении семантики полного языка программирования с использованием аксиоматического метода для каждого вида операторов языка должны быть сформулированы аксиома или правило логического вывода. Но определение аксиом и правил логического вывода для некоторых операторов языков программирования - очень сложная задача. Трудно построить «множество основных аксиом, достаточно ограниченное для того, чтобы избежать противоречий, но достаточно богатое для того, чтобы служить отправной точкой для доказательства высказываний о программах» (Э. Дейкстра). Напрашивающимся решением такой проблемы является разработка языка, подразумевающего использование аксиоматического метода, т.е. содержащей только те операторы, для которых могут быть написаны аксиомы или правила логического вывода. К сожалению, подобный язык оказался бы слишком маленьким и простым что отражает нынешнее состояние аксиоматической семантики как науки. Аксиоматическая семантика является мощным инструментом для исследований в области доказательств правильности программ, она также создает великолепную основу для анализа программ, как во время их создания, так и позднее. Однако ее полезность при описании содержания языков программирования весьма ограничена как для пользователей языка, так и для разработчиков компиляторов. Денотационная семантика Денотационная семантика - самый строгий широко известный метод описания значения программ. Она прочно опирается на теорию рекурсивных функций. Всестороннее рассмотрение денотационной семантики - длительное и сложное дело. Здесь мы познакомимся лишь с ее основными принципами. Основной концепцией денотационной семантики является определение для каждой сущности языка некоего математического объекта и некоей функции, отображающей экземпляры этой сущности в экземпляры этого математического объекта. Поскольку объекты определены строго, то они представляют собой точный смысл соответствующих сущностей. Сама идея основана на факте существования строгих методов оперирования математическими объектами, а не конструкциями языков программирования. Сложность использования этого метода заключается в создании объектов и функций отображения. Название метода «денотационная семантика» происходит от английского слова denote(обозначать), поскольку математический объект обозначает смысл соответствующей синтаксической сущности. Для введения в денотационный метод мы используем очень простую языковую конструкцию - двоичные числа. Синтаксис этих чисел можно описать следующими грамматическими правилами: <двоичное_число> → 0; | 1; | <двоичное_число>0; | <двоичное_число>1. Для описания двоичных чисел с использованием денотационной семантики и грамматических правил, указанных выше, их фактическое значение связывается с каждым правилом, имеющим в своей правой части один терминальный (основной) символ. Объектами в данном случае являются десятичные числа. В нашем примере значащие объекты должны связываться с первыми двумя правилами. Остальные два правила являются, в известном смысле, правилами вычислений, поскольку они объединяют терминальный символ, с которым может ассоциироваться объект, с нетерминальным, который может представлять собой некоторую конструкцию. Пусть область определения семантических значений объектов представляет собой множество неотрицательных десятичных целых чисел Nat. Это именно те объекты, которые мы хотим связать с двоичными числами. Семантическая функция Мb отображает синтаксические объекты в объекты множества N согласно указанным выше правилам. Сама функция Мbопределяется следующим образом: Мb('0') = 0, Мb('1')=1; Мb(<двоичное_число> '0') = 2 × Мb(<двоичное_число>); Мb(<двоичное_число> ‘1’) = + 2 × Мb(<двоичное_число>) + 1. Мы заключили синтаксические цифры в апострофы, чтобы отличать их от математических цифр. Отношение между этими категориями подобно отношениям между цифрами в кодировке ASCII и математическими цифрами. Когда программа считывает число как строку, то прежде, чем это число сможет использоваться в программе, оно должно быть преобразовано в математическое число. Пример 2.7. Описание значения десятичных синтаксических литеральных констант. <десятичное_число> → 0|1|2|3|4|5|6|7|8|9; | <десятичное_число> (0|1|2|3|4|5|6|7|8|9). Денотационные отображения для этих синтаксических правил имеют следующий вид: Md(‘0') = 0, Md('1') = 1, ,..., Md('9') = 9; Мd(<десятичное_число> '0') = 10 × Мd(<двоичное_число>); Мd(<десятичное_число> ‘1’) = 10 × Мd(<десятичное_число>) + 1; … Мd(<десятичное_число> '9') = 10 × Мd(<десятичное_число>) + 9. Денотационную семантику программы можно определить в терминах изменений состояний идеального компьютера. Подобным образом определялись операционные ceмантики, приблизительно так же определяются и денотационные. Правда, для простоты они определяются только в терминах значений всех переменных, объявленных в программе. Операционная и денотационная семантики различаются тем, что изменения состояний в операционной семантике определяются запрограммированными алгоритмами, а в денотационной семантике они определяются строгими математическими функциями. Пусть состояние s программы определяется следующим набором упорядоченных пар: {<i1, v1>, <i2, v2>, …, <in, vn>}. Каждый параметр iявляется именем переменной, а соответствующие параметры v являются текущими значениями данных переменных. Любой из параметров v может иметь специальное значение undef, указывающее, что связанная с ним величина в данный момент не определена. Пусть VARMAP - функция двух параметров, имени переменной и состояния программы. Значение функции VARMAP(ik, s) равно vk(значение, соответствующее параметру ik в состоянииs). Большинство семантических функций отображения для программ и программных конструкций отображают состояния в состояния. Эти изменения состояний используются для определения смысла программ и программных конструкций. Отметим, что такие языковые конструкции, как выражения, отображаются не в состояния, а в величины. Выражения являются основой большинства языков программирования. Более того, мы имеем дело только с очень простыми выражениями. Единственными операторами являются операторы + и ×; выражения могут содержать не более одного оператора; единственными операндами являются скалярные переменные и целочисленные литеральные константы; круглые скобки не используются; значение выражения является целым числом. Ниже следует описание этих выражений в форме БНФ: <выражение> → <десятичное_число>| <переменная> | <двоичное_выражение> <двоичное_выражение> → <выражение_слева><оператор> <выражение_справа> <оператор> → + | × Единственной рассматриваемой нами ошибкой в выражениях является неопределенное значение переменной. Разумеется, могут появляться и другие ошибки, но большинство из них зависят от машины. Пусть Z - набор целых чисел, a error - ошибочное значение. Тогда множество Z {error} является множеством значений, для которых выражение может быть вычислено. Ниже приведена функция отображения для данного выражения Е и состояния s. Символ обозначает равенство по определению функции. Me(<выражение>, s) |