![]() |
|
Модераторы: Partizan, gambit |
![]()
|
|
| Xenon |
|
|||
|
Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1529 Регистрация: 12.4.2006 Репутация: 1 Всего: 50 |
Даже почитав начало Рихтера не совсем понял как именно происходит вызов виртуальных методов именно на НИЗКОМ уровне в .NET. Я так понял там всю дорогу юзается GetType? Только где?
|
|||
|
||||
| tol05 |
|
|||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1632 Регистрация: 21.12.2006 Где: Харьков Репутация: 63 Всего: 170 |
Оо... Xenon, отличный вопрос...Для начала три тезиса:
1. Для вызова виртуального метода используется реальный тип объекта, а не тип ссылки, которой он присвоен. Соответственно для невиртуального метода используется тип ссылки, которая на него ссылается. 2. Каждый объект имеет ссылку на внутреннюю сруктуру данных (которая размещается в памяти домена) - т.н. объект-тип. А этот уже объект-тип имеет ссылку на таблицу методов (статических, виртуальных и обычных экземплярных, всех, которыми тип может оперировать)... Пользуясь случаем - еще раз спасибо Рихтеру за науку, дай Бог ему здоровья 3. для вызово невиртуальных методов используется директива call, для виртуальных - callvirt Ну а теперь все вместе. Допустим, есть
CLR должна найти метод F() и применить его к obj (допустим, что все проверки выполнились, все компилится и все допустимо - для простоты). Как найти метод? 1. obj - ссылка в стеке на область памяти (в куче), где расположены поля объекта. Нашли этот адрес и залезли в кучу. 2. По нужному адресу находим ссылку на объект-тип (Рихтер писал, что любой объект в куче имеет два члена - syncIndex и указатель на объект-тип). Идем по этому адресу 3. Находим эту служебную структуру и в ней отыскиваем таблицу методов этого типа. Тип у нас - Child и метод F() - переопределен. 4. Вызываем метод F виртуально. Если бы F() был не виртуальным методом, то вместо шага 2 (искать адрес объекта-типа в полях объекта) CLR просто взяла бы адрес объекта-типа ссылки на объект (т.е. Base). Вот и отличие callvirt от call. Вот так, вроде GetType() - как раз и указывает на объект-тип. дополнительно к Рихтеру советую почитать это -------------------- На хорошей работе и сны хорошие снятся. |
|||
|
||||
| Xenon |
|
||||||||
|
Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1529 Регистрация: 12.4.2006 Репутация: 1 Всего: 50 |
Ну еще callvirt от call отличается проверкой ссылки на null.
Да с syncIndex и т.д. все понятно. То есть получается, что, грубо говоря, для объекта вызывается GetType, узнается реальный тип, смотрится таблица методов по реальному типу, видим там override. Дальше что? Глядем в базовый? Нечетко я понимаю смысл "вызываем виртуально". Что конкретно происходит в случаях:
При
? Узнаем, чтo obj есть BaseC, допустим, видим там override у метода Foo. Значит "нужно вызывать виртуально". Значит мы берем статический тип - в моем случае BaseA и смотрим у него на метод Foo - он помечен как virtual, значит гуляем в производный (BaseB) - там, допустим, тоже virtual - значит идем в BaseC и попадаем в наш тип, там override, значит это Foo относящейся к этой группе методов Foo и вообще мы попали уже в BaseC, поэтому вызываем Foo из BaseC? А если бы у BaseB был бы метод Foo почен как override? Мы бы пошли опять же, поняли, что у нас obj типа BaseC а статический тип - BaseA. Пошли бы в BaseA - увидели бы virtual, стали бы искать в производном override метод, нашли бы его и успокоившись вызвали? Добавлено через 2 минуты Даже если то, что я написал не бред, то все равно интересно именно как CLR гуляет так по классам или это уже слишком мутно? |
||||||||
|
|||||||||
| tol05 |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1632 Регистрация: 21.12.2006 Где: Харьков Репутация: 63 Всего: 170 |
Это как раз и значит, что по полю "object-type" объекта ищется объект-тип с нужной таблицей методов. Если бы "вызывали невиртуально", то не нужно было бы для нахождения таблицы методов выяснять real run-time type объекта, достаточно взять "захардкоданный" в твоем случае : никто никуда не гуляет
При запуске приложения метаданные типов считались и создались те пресловутые "внутренние структуры памяти", каждая - со своей таблицей методов. Когда компилировался код, то в местах вызовов Foo() была вставлена директива callvirt (а не call). Когда код работает, то при каждом callvirt ищется таблица методов (реального типа, а не типа ссылки), берется адрес входа в метод и метод выполняется. Ну а при создании кода метода JIT-ом - для каждого типа создается свой код и адрес точки входа в него записывается в таблицу методов наших трех типов. Вот из статьи, линк, на которую я давал
Одним словом : вся соль - в call и callvirt. P.S. можно еще вглубь двигаться, заметить, что компилятор может и невиртуальный метод вызывать как виртуальный, через callvirt (здесь такой случай обсуждается), но думаю, что это не нужно. Это уже ньюансы реализации компилятора, оптимизаторов, контекстов использования. И это только запутает тему Рихтер "Вызов виртуальных методов, свойств и событий в CLR"
Это сообщение отредактировал(а) tol05 - 15.11.2007, 13:35 -------------------- На хорошей работе и сны хорошие снятся. |
||||||||
|
|||||||||
| Xenon |
|
|||
|
Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1529 Регистрация: 12.4.2006 Репутация: 1 Всего: 50 |
Все равно не понимаю. Получается мы просто получаем реальный тип и вызываем метод, который находим в его метаданных. Так почему тогда если в BaseA сделать метод не виртуальным вызовется Base.Foo()? Да даже если и виртуальный, то все равно, если в BaseB будет просто метод Foo, то мы спотыкнемся. На лицо какая-то связь в виде цепочки, поэтому я и предположил, что CLR гуляет вот так ...
Хорошо, метод переопределен - то есть в BaseB у нас virtual, а в BaseC override. Почему вызывается метод BaseA, если он вообще без меток стоит? |
|||
|
||||
| tol05 |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1632 Регистрация: 21.12.2006 Где: Харьков Репутация: 63 Всего: 170 |
если метод не виртуальный - для его нахождения используется тип ссылки
если виртуальный - то для его нахождения используется реальный тип созданного объекта
но объявления типов должно выглядеть так
в твоем же коде метод Foo() определен как виртуальный и в BaseA, и в BaseB. Т.е. это два разных метода. Поэтому компилятор и warning выбрасывает - он не знает, переопределить или скрыть Foo() ты хочешь. И цепочка полиморфизма BaseA-BaseC рвется. Подробно этот случай описан в ECMA 334, параграф 8.3 "Versioning" (здесь) Класс BaseC переопределяет Foo(), но только Foo() своего базового класса (класса BaseB). Полиморфизм все равно работает, но только для
-------------------- На хорошей работе и сны хорошие снятся. |
||||||||
|
|||||||||
| tol05 |
|
||||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1632 Регистрация: 21.12.2006 Где: Харьков Репутация: 63 Всего: 170 |
вот MSIL
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
видно, что для твоего кода создано два слота памяти, для моего - один. Дальше (не беде повторяться
-------------------- На хорошей работе и сны хорошие снятся. |
||||||||||
|
|||||||||||
| tol05 |
|
||||||||
![]() Эксперт ![]() ![]() ![]() Профиль Группа: Участник Клуба Сообщений: 1632 Регистрация: 21.12.2006 Где: Харьков Репутация: 63 Всего: 170 |
эпиграф "Как говорится кто о чем, а вшивый - о бане".
Это снова я. "... Гляжу - а вдоль дороги - мертвые с косами стоят! И тишина ..." сам перечитал... результаты исследования памяти для варианта, сокращенно именуемого как (virtual virtual override)
результаты исследования памяти для варианта, сокращенно именуемого как (virtual override override)
Я делаю такие выводы:
В версии "virtual virtual override" определено два слота уже в классе BaseB. Причем адрес BaseА.Foo() не замещен, вместо этого добавлен слот BaseB.Foo() со своим адресом. Когда создается класс BaseC, то при создании его таблицы методов замещается адрес метода BaseB.Foo(), адрес метода BaseА.Foo() копируется, аналогично классу BaseB. При вызове Foo() объекта BaseC через ссылку на BaseА идет вызов маппинг на слот BaseА.Foo() и вызывается старая версия BaseА.Foo() При вызове Foo() объекта BaseC через ссылку на BaseB идет вызов маппинг на слот BaseB.Foo(), но при создании BaseC адрес BaseB.Foo() в его таблице методов был замещен и вызывается версия BaseC.Foo() Вот почему же при той же сигнатуре классов правильно работает
ну вот, теперь ИМХО все. Это сообщение отредактировал(а) tol05 - 16.11.2007, 16:13 -------------------- На хорошей работе и сны хорошие снятся. |
||||||||
|
|||||||||
![]()
|
| Прежде чем создать тему, посмотрите сюда: | |
|
|
Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов. Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :) Так же не забывайте отмечать свой вопрос решенным, если он таковым является :) Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle. |
| 0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей) | |
| 0 Пользователей: | |
| « Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема » |
|
|
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности Powered by Invision Power Board(R) 1.3 © 2003 IPS, Inc. |