В.А. Серебряков - Теория и реализация языков программирования (1114953), страница 36
Текст из файла (страница 36)
9.17.Рис. 9.17Доступ к члену класса A, B или C реализуется в точности так же, каки для единичного наследования: компилятор знает положение каждого членав объекте и порождает соответствующий код.Если объект размещен в памяти согласно первой диаграмме: сначала частьA объекта, а затем части B и C , то вызов функции-члена класса A илиC будет таким же, как вызов функции-члена при единичном наследовании.Вызов функции-члена класса B для объекта, заданного указателем на C ,реализуется несколько сложнее. Рассмотрим следующий пример:C ∗ pc = new C ;pc → bf(2);Функция B :: bf() естественным образом предполагает, что ее параметрthis является указателем на B .
Чтобы получить указатель на часть B объектаC , следует добавить к указателю pc смещение B относительно C - константувремени компиляции, которую мы будем называть delta(B ). Соотношениеуказателя pc и указателя this, передаваемого в B ::bf, показано на рис.
9.18.Рис. 9.187*196Глава 9. Генерация кода9.9.3. Единичное наследование и виртуальные функции. Если классbase содержит виртуальную функцию vf , а класс derived, порожденныйпо классу base, также содержит функцию vf того же типа, то обращениек vf для объекта класса derived вызывает derived :: vf даже при доступечерез указатель или ссылку на base. В таком случае говорят, что функцияпроизводного класса подменяет (override) функцию базового класса. Если,однако, типы этих функций различны, то функции считаются различнымии механизм виртуальности не включается.Виртуальные функции можно реализовать при помощи таблицы указателей на виртуальные функции vtbl. В случае единичного наследования таблицавиртуальных функций класса будет содержать ссылки на соответствующиефункции, а каждый объект данного класса будет содержать указатель на таблицу vtbl.class A {public:int a;virtual void f (int);virtual void g (int);virtual void h(int);};class B : public A {public:int b;void g (int);};class C : public B {public:int c;void h(int);};Объект класса C будет выглядеть примерно так, как показано на рис.
9.19.Рис. 9.199.9.4. Множественное наследование и виртуальные функции. Примножественном наследовании виртуальные функции реализуются несколькосложнее. Рассмотрим следующие объявления:9.9. Трансляция объектно-ориентированных свойств языков программирования197class A {public:virtual void f (int);};class B : {public:virtual void f (int);virtual void g (int);};class C : public A, public B {public:void f ();};Поскольку класс A порожден по классам A и B , каждый из следующихвызовов будет обращаться к C :: f () (при условии, что каждый из трехуказателей смотрит на объект класса C ):pa → f ()pb → f ()pc → f ()Рассмотрим для примера вызов pb → f ().
При входе в C :: f указательthis должен указывать на начало объекта C , а не на часть B в нем. Во времякомпиляции, вообще говоря, не известно, указывает ли pb на часть B в C , —например, из-за того, что идентификатору pb может быть присвоен простоуказатель на объект B . Так что величина delta(B), упомянутая выше, можетбыть различной для разных объектов в зависимости от структуры классов,порождаемых из B , и должна где-то храниться во время выполнения.Поскольку это смещение нужно только для виртуального вызова функции,логично хранить его в таблице виртуальных функций.Указатель this, передаваемый виртуальной функции, может быть вычислен путем вычитания смещения объекта, для которого была определена виртуальная функция, из смещения объекта, для которого она вызвана, а затемвычитания этой разности из указателя, используемого при вызове. Здесьзначение delta(B ) будет необходимо для поиска начала объекта (в нашемслучае C ), содержащего B , по указателю на B .
Сгенерированный код вычтетзначение delta(B ) из значения указателя, так что хранится смещение со знаком минус, −delta(B ). Объект класса C будет выглядеть так, как показанона рис. 9.20.Таблица виртуальных функций vtbl для B в C отличается от vtbl дляотдельно размещенного B . Каждая комбинация базового и производногоклассов имеет свою таблицу vtbl. В общем случае объекту производного198Глава 9. Генерация кодаРис. 9.20класса требуется таблица vtbl для каждого базового класса плюс таблица дляпроизводного класса, не считая того, что производный класс может разделятьтаблицу vtbl со своим первым базовым классом.
Таким образом, для объектатипа C в этом примере требуются две таблицы vtbl (таблица для A в Cобъединена с таблицей для oбъекта C , и еще одна таблица нужна для объектаB в C ).9.9.5. Виртуальные базовые классы с виртуальными функциями.При наличии виртуальных базовых классов построение таблиц для вызововвиртуальных функций становится более сложным. Рассмотрим следующиеобъявления:class W {public:virtual void f ();virtual void g ();virtual void h();virtual void k ();};class M W : public virtual W {public:void g ();};class BW : public virtual W {public:void f ();};class BM W : public BW , public M W , public virtual W {public:void h();};Отношение наследования для этого примера может быть изображено в виде ациклического графа (рис. 9.21).Функции-члены класса BM W могут использоваться, например, так:9.9.
Трансляция объектно-ориентированных свойств языков программирования199Рис. 9.21void g(BM W ∗ pbmw){pbmw → f (); // вызывает BW :: f ()pbmw → g(); // вызывает M W :: g()pbmw → h(); // вызывает BM W :: h()}Рассмотрим теперь следующий вызов виртуальной функции f ():void h(BM W ∗ pbmw){M W ∗ pmw = pbmw;pmw → f (); // вызывает BW :: f (), потому что// pbmw указывает на BM W , для которого f берется из BW !}Виртуальный вызов функции по одному пути в структуре наследованияможет породить обращение к функции, переопределенной на другом пути.Структуры объектов класса BM W и его таблиц виртуальных функцийvtbl могут выглядеть так, как показано на рис.
9.22.Виртуальной функции должен быть передан указатель this на объекткласса, в котором эта функция описана. Поэтому следует хранить смещениедля каждого указателя функции из vtbl. Когда объект размещен в памятитак, как это изображено на рис. 9.22, смещение, хранимое с указателем виртуальной функции, исчисляется вычитанием смещения класса, для которогоэта таблица vtbl создана, из смещения класса, поставляющего эту функцию.Рассмотрим пример:void callvirt(w∗ pw){ pw → f ();}main (){ callvirt(new BM W );}200Глава 9.
Генерация кодаРис. 9.22В функции main вызов callvirt с указателем на BM W требует приведения к указателю на W , поскольку функция callvirt ожидает параметртипа W ∗ . Так как функция callvirt вызывает f () (через указатель на BM W ,преобразованный к указателю на W ), будет использована таблица vtbl классаW (в BM W ), где указано, что экземпляром виртуальной функции f (), которую нужно вызвать, является BW :: f (). Чтобы передать функции BW :: f ()указатель this на BW , указатель pw должен быть вновь приведен к указателю на BM W (вычитанием смещения для W ), а затем к указателю на BW(добавлением смещения BW в объекте BM W ). Значение смещения BWв объекте BM W минус смещение W в объекте BM W и есть смещение,хранимое в строке таблицы vtbl для w в BM W для функции BW :: f ().9.10.
Генерация оптимального кодаметодами сопоставления образцов9.10.1. Сопоставление образцов. Техника генерации кода, рассмотренная выше, основывалась на однозначном соответствии структуры промежуточного представления с описывающей это представление грамматикой.Недостатком такого «жесткого» подхода является то, что, как правило, однуи ту же программу на промежуточном языке можно реализовать многимиразличными способами в системе команд машины.
Эти разные реализациимогут иметь различную длину, время выполнения и другие характеристики.Для генерации более качественного кода примени́м подход, изложенный в настоящей главе.Этот подход основан на понятии «сопоставления образцов»: командаммашины сопоставляются некоторые «образцы», вхождения которых ищутся9.10. Генерация оптимального кода методами сопоставления образцов201в промежуточном представлении программы, и делается попытка «покрыть»промежуточную программу такими образцами. Если это удается, то по образцам восстанавливается программа уже в кодах. Каждое такое покрытиесоответствует некоторой программе, реализующей одно и то же промежуточное представление.Рис. 9.23На рис. 9.23 показано промежуточное дерево для оператора a = b[i] + 5,где a, b, i — локальные переменные, хранимые со смещениями x, y , zсоответственно в областях данных с одноименными адресами.Элемент массива b занимает память в одну машинную единицу.
0-местнаяоперация const возвращает значение атрибута соответствующей вершиныпромежуточного дерева, указанного на рисунке в скобках после оператора.Одноместная операция @ означает косвенную адресацию и возвращает содержимое регистра или ячейки памяти, имеющей адрес, задаваемый аргументомоперации.В табл. 9.2 показан пример сопоставления образцов машинным командам.Приведены два варианта задания образца: в виде дерева и в виде правилаконтекстно-свободной грамматики. Для каждого образца указана машиннаякоманда, реализующая этот образец, и стоимость этой команды.В каждом дереве-образце корень или лист может быть помечен терминальным и/или нетерминальным символом.
Внутренние вершины помеченытерминальными символами — знаками операций. При наложении образцана дерево выражения, во-первых, терминальный символ образца должен соответствовать терминальному символу дерева и, во-вторых, образцы должны«склеиваться» по типу нетерминального символа, т. е. тип корня образца202Глава 9. Генерация кодадолжен совпадать с типом вершины, в которую образец подставляется корнем.Допускается использование «цепных» образцов, т. е. образцов, корню которых не соответствует терминальный символ и которые имеют единственныйэлемент в правой части.
Цепные правила служат для приведения вершинк одному типу. Например, в рассматриваемой системе команд одни и те жерегистры используются как для целей адресации, так и для вычислений.Если бы в системе команд для этих целей использовались разные группырегистров, то в грамматике команд могли бы использоваться разные нетерминалы, а для пересылки из адресного регистра в регистр данных могли быиспользоваться соответствующая команда и образец.Т а б л и ц а 9.29.10. Генерация оптимального кода методами сопоставления образцов203Нетерминалы Reg на образцах могут быть помечены индексом (i или j ),что (неформально) соответствует номеру регистра и служит лишь для пояснения смысла использования регистров.