Мысленный эксперимент: ООП без классов

Персональный блог одноименного форумчанина. Человека и парохода, не побоюсь этого сравнения :)

Модератор: tyomitch

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Мысленный эксперимент: ООП без классов

Сообщение tyomitch » 18.08.2007 (Сб) 21:01

Предпосылки к этому посту:
1) разделение одним завсегдатаем форума всего программирования на ООП, олицетворением которого объявляются C++ и .net, и не-ООП, олицетворением которого объявляется QBasic;
2) вон тот мой код;
3) обсуждение делегатов с Хакером в ICQ.


* 1. Делегаты: крэш-курс

Делегат – это ссылка на конкретный метод конкретного объекта. Она состоит из двух частей: ссылки на объект и процедурного указателя. Вызов по делегату просто подставляет первую ссылку параметром Me при вызове по процедурному указателю.

Делегаты нисколько не ограничены миром .net: например, они есть в Delphi, где названы method pointers. Проблема с COM-делегатами в том, что в них должна быть сильная ссылка на объект; значит, сами COM-делегаты должны вести подсчёт ссылок на себя, и саморазрушаться строго тогда, когда необходимо. Проще всего добиться этого, реализовав делегаты в виде COM-объектов. Тогда даже синтаксис вызова метода через делегат окажется на 100% COM-совместимым, если реализовать его специфический “operator()” в виде функции по умолчанию (DISPID_VALUE).

Такие COM-объекты из одного члена–оператора() принято называть функторами. Так их и будем называть в дальнейшем, чтобы отвязаться от .нецкого неологизма и ненужных .нецких ассоциаций, сопровождающих его упоминание.


* 2. Мечты, мечты, где ваша сладость?

Представим себе, что в VB появилась поддержка функторов. Что оператор AddressOf для методов класса возвращает именно такой, как описано выше, функтор. Например: (совместимость использованного здесь синтаксиса с существующими языками не преследовалась)
Код: Выделить всё

' MyObject – некий класс
' код класса MyObjects:

Private m_AllMyObjects() As MyObject

Public Delegate EnumMyObjectsProc(ByVal o As MyObject) As Boolean

Public Sub EnumMyObjects(ByVal callback As EnumMyObjectsProc)
Dim o As MyObject
    For Each o In m_AllMyObjects
        If Not callback(o) Then Exit Sub
    Next
End Sub


' код класса AnotherClass:
Private m_AllMyObjects As MyObjects
Private m_AllObjectsValid As Boolean
Private m_ValidationParameters As Variant

Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
    m_ValidationParameters = Parameters
    m_AllObjectsValid = True
    m_AllMyObjects.EnumMyObjects AddressOf ValidateObject
    ValidateObjects = m_AllObjectsValid
End Function

Private Function ValidateObject(ByVal obj As MyObject) As Boolean
    m_AllObjectsValid = obj.Validate(m_ValidationParameters)
    ValidateObject = m_AllObjectsValid
End Function



Здесь предполагается, что в строке со словом AddressOf создастся экземпляр невидимого класса EnumMyObjectsProc, состоящего из одного безымянного метода Public Function [](ByVal o As MyObject) As Boolean и двух полей: m_Target As Object, m_TargetVTblOffset As Long. Контроль типов возлагается на компилятор, который будет гарантировать, что метод объекта m_Target, находящийся в его vtable по смещению m_TargetVTblOffset, действительно имеет подходящую сигнатуру. В следующей строчке, после выхода из MyObjects::EnumMyObjects, ссылка на функтор исчезнет, и он саморазрушится, освобождая хранящуюся в нём ссылку на экземпляр класса AnotherClass. При создании функтора не обязательно указывать метод своего объекта: можно написать и AddressOf SomeObject.AnySuitableMethod

До сих пор всё достаточно реалистично, правда?


* 3. Анонимные методы

Теперь вообразим себе конструкцию AddressOf Delegate … End Delegate, которая позволяет внутри одного метода определить другой метод (а внутри него, возможно, ещё методы) того же самого класса. Потом для внутреннего (безымянного) метода создаётся функтор, как обычно оператором AddressOf. Например, код класса AnotherClass перепишется следующим образом:
Код: Выделить всё
Private m_AllMyObjects As MyObjects
Private m_AllObjectsValid As Boolean
Private m_ValidationParameters As Variant

Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
    m_ValidationParameters = Parameters
    m_AllObjectsValid = True
    m_AllMyObjects.EnumMyObjects AddressOf Delegate(ByVal obj As MyObject) As Boolean
        m_AllObjectsValid = obj.Validate(m_ValidationParameters)
        Delegate = m_AllObjectsValid
    End Delegate
    ValidateObjects = m_AllObjectsValid
End Function



Уже компактнее, но ещё недостаточно круто. Возложим на компилятор ещё больше работы: пусть он найдёт внутри кода анонимного метода все ссылки на локальные переменные (включая параметры) того метода, внутри которого он определён, и добавит все эти переменные полями внутрь функтора, рядом со ссылкой на экземпляр AnotherClass, необходимой для доступа к членам этого класса. Теперь m_TargetVTblOffset внутри функтора уже не нужна: весь код анонимного метода будет скомпилирован в единственный метод функтора – потому что доступ к этому методу иначе, чем через функтор, всё равно невозможен (в отличие от предыдущего примера). И даже больше: если окажется, что код анонимного метода не ссылается ни на какие члены AnotherClass, то не нужно даже m_Target. В этом случае функтор сможет даже пережить смерть того объекта, из метода которого он был создан (в обычном случае ссылка изнутри функтора удерживает в живых такой объект даже тогда, когда прочих ссылок на этот объект нет).

Но просто добавить копии всех локальных переменных внутрь функтора мало: если анонимный метод поменяет значения каких-то из них, то внешний метод должен увидеть эти изменения. Сохранить внутри функтора ссылки на них тоже недостаточно: при возврате из внешнего метода все его локальные переменные уничтожатся, а экземпляры вложенных в него анонимных методов могут продолжать жить, если на них остались ссылки. Так что придётся весь блок локальных переменных, на которые ссылаются (“замыкаются”) вложенные методы, выделить в ещё один невидимый класс. При входе во внешнюю процедуру создастся экземпляр такого класса, в него скопируются все нужные локальные переменные, и весь доступ к ним изнутри метода будет идти через ссылку на этот невидимый объект. Все создаваемые внутри метода экземпляры анонимных функций тоже получат ссылку на этот объект. При выходе из внешнего метода он освободит свою ссылку; когда все созданные внутри него анонимные функции завершатся, они тоже освободят свои ссылки, и этот невидимый объект самоуничтожится. Таким образом, у функтора всегда оказывается максимум два поля: опциональное m_Target, если он ссылается на члены своего объекта, и либо m_TargetVTblOffset (для именованных методов), либо m_Context (для анонимных методов).

С использованием замыканий, AnotherClass упрощается донельзя:
Код: Выделить всё
Private m_AllMyObjects As MyObjects

Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
    ValidateObjects = True
    m_AllMyObjects.EnumMyObjects AddressOf Delegate(ByVal obj As MyObject) As Boolean
        ValidateObjects = obj.Validate(Parameters)
        Delegate = ValidateObjects
    End Delegate
End Function



Здесь в блок переменных, выделяемый в невидимый класс, попадут параметр Parameters и локальная переменная ValidateObjects. Однако, если бы внутри нашего анонимного метода был определён ещё один анонимный метод, то последний не смог бы ссылаться на локальную переменную Delegate своего внешнего метода, т.к. внутри него под этим именем была бы доступна собственная локальная переменная. Так что имеет смысл разрешить именовать вложенные методы:
Код: Выделить всё
Private m_AllMyObjects As MyObjects

Public Function ValidateObjects(ByVal Parameters As Variant) As Boolean
    ValidateObjects = True
    m_AllMyObjects.EnumMyObjects AddressOf Delegate ValidateObject(ByVal obj As MyObject) As Boolean
        ValidateObjects = obj.Validate(Parameters)
        ValidateObject = ValidateObjects
    End Delegate
End Function



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


(Продолжение уже сочинено, но будет запощено отдельно, для поддержания интриги.)
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.08.2007 (Сб) 21:29

Такие COM-объекты из одного члена–оператора() принято называть функторами.


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

Насколько я знаю, функторы есть в JavaScript-е (ну и наверное в Джаве), но я её там ни разу не пробовал пользоваться :)

Поэтому я как пример возьму PHP.
В php такой код:

Код: Выделить всё
$function_name();
означает вызов функции, имя которой содержится в переменной $function_name.


Пример:
Код: Выделить всё

function foo1()
{
     echo "Привет, я Фу1";
}

function foo2()
{
      echo "Привет, я Фу2";
}

...

function foo999()
{
     echo "Привет, я Фу999";
}


for($i=1;$i<=999;$i++)
{
      $bar = "foo$i";     // Для тех кто не плохо знаком с PHP: Эта
                                // конструкция в VBScript-е выглядела бы так:
                                // bar = "foo" + CStr(i)
                                // её также можно записать как:
                                // $bar = "foo" .  $i  // Точка - оператор конкатенации
      $bar();
}


Итак, этот код последовательно вызовет все функции начиная с foo1 и заканчивая foo999.

В скобках резуммется могут быть аргументы:

Код: Выделить всё
function TestMe($arg1, $arg2)
{
       return $arg1 + $arg2;
}

$zu = "TestMe";
echo $zu(5,3);     // Выведет в stdout (или в OB) восьмёрку


Данная возможность используется везде в PHP где это возможно для организации вызовов процедур обратного вызова (callback-ов, проще говоря).


Теперь перейдём к делегатам :)

Разумеется в PHP не обошлось и без этого.

Делегат в PHP - функтор. Но в отличие от обычного функтора, это не строка а массив из двух элементов - ссылка на класс и строка "имя метода".


Приведу пример:

Код: Выделить всё
class CTestClass
{
      function FooBar($za)
      {
          echo "Вы передали: $za";
      }

}


$myclass = new CTestClass();

$my_functor = array(&$myclass, "FooBar");

$my_functor("Гы!!!");  // "Вы передали: Гы!!!";



Здесь: array - функция, принимающая неогр. кол-во аргументов, и создающая из них массив, &$myclass - получение ссылки $myclass (можно и без этого, но в определённых случаях можно нарваться на конструктор копирования (наверное - сам я ни разу не нарывался)).

Поэтому в PHP есть возможность устанавливать в качестве callback-а как обычную функцию, так и метод класса. И работать это без пробем будет и со встроенными функциями, принимающими callback (например с usort()-ом), и с самописными.

Дальше я пока прочитать не успел - сейчас буду.

Пост написан на одном дахании и возможно содержит ошибки :)
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 18.08.2007 (Сб) 21:39

Хакер писал(а):
Такие COM-объекты из одного члена–оператора() принято называть функторами.


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

Я не утверждал, что "функторами принято называть только такие COM-объекты, и ничего кроме этого".
Я утверждал то, что я утверждал.


Связь экскурса в PHP с темой моего поста от меня ускользнула. Она была?
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 18.08.2007 (Сб) 21:42

Я не утверждал, что "функторами принято называть только такие COM-объекты, и ничего кроме этого".
Я утверждал то, что я утверждал.


Чтобы всем было понятно я внёс ясность в неоднозначную формулировку.

Связь экскурса в PHP с темой моего поста от меня ускользнула. Она была?


Почти нет. Просто захотелось развить тему функторов :)

Ну и подтвердить это:
Делегаты нисколько не ограничены миром .net: например, они есть в Delphi, где названы method pointers.

примером ещё одного не-нэцкого языка.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 18.08.2007 (Сб) 22:51

А можно мне тут привести код, который я недавно накатал(так , приспичило, со всеми случается), который создает VB-шный йунктор(в понимании автора трэда), принимая в качестве параметра ссылку на idispatch и имя метода? Мартышкин труд, разумеется, но все же.
Код: Выделить всё
#include <windows.h>

extern "C" IDispatch* __stdcall CreateInvokablePointer(IDispatch* obj,BSTR lpName)
{
   InvokablePointer* x=new InvokablePointer;
   x->obj=obj;
   x->cnt=1;
   if(obj->GetIDsOfNames(IID_NULL,&lpName,1,0,&x->o_dispId)<0)
   {
      delete x;
      return 0;
   }
   return x;
}

struct InvokablePointer:IDispatch
{
   ULONG cnt;

   ULONG STDMETHODCALLTYPE AddRef()
   {
      return ++cnt;
   }

   ULONG STDMETHODCALLTYPE Release()
   {
      --cnt;
      if(!cnt)
         delete this;
      return cnt;
   }

   HRESULT STDMETHODCALLTYPE QueryInterface(
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject)
   {
      if(riid==IID_IUnknown || riid==IID_IDispatch)
      {
         ++cnt;
         *ppvObject=(IUnknown*)this;
      }
      else
         return E_NOINTERFACE;
      return S_OK;
   }

   HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
            /* [out] */ UINT __RPC_FAR *pctinfo)
   {
      return E_NOTIMPL;
   }
   HRESULT STDMETHODCALLTYPE GetTypeInfo(
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo)
   {
      return E_NOTIMPL;
   }
   HRESULT STDMETHODCALLTYPE GetIDsOfNames(
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId)
   {
      return E_NOTIMPL;
   }

   HRESULT STDMETHODCALLTYPE Invoke(
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr)
   {
      if(!dispIdMember)
         return obj->Invoke(o_dispId,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr);
      else
         return DISP_E_MEMBERNOTFOUND;
   }
   IDispatch* obj;
   DISPID o_dispId;
};
(конечно не совсем в тему, но не создавать же отдельный тред?)
Последний раз редактировалось ANDLL 20.08.2007 (Пн) 10:44, всего редактировалось 1 раз.
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 18.08.2007 (Сб) 23:07

Как минимум, в твоём QueryInterface нехватает AddRef.
Изображение

keks-n
Доктор VB наук
Доктор VB наук
Аватара пользователя
 
Сообщения: 2509
Зарегистрирован: 19.09.2005 (Пн) 17:17
Откуда: г. Москва

Сообщение keks-n » 19.08.2007 (Вс) 0:29

Гм. Дегелирование DISPID=0 на другой - примитивно, но идея неплоха. Только тут действительно код кривоват.
Возврат E_NOTIMPL - некрасиво: в геткаунт вернуть в переменной ноль, а в гетинфо ругаться на индекс, а из GetIDsOfNames везвращать DISP_E_MEMBERNOTFOUND. Не говоря уже о том, что Invoke не проверяет DISPID на 0, а сливает все вызовы на оригинал меняя DISPID. И не важно, что туда могли передать другой предопределённый DISPID.
Изображение

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 20.08.2007 (Пн) 10:47

QueryInterface исправлен
Проверку DISPID поставил, хотя это так скзаать "на вкус"
Комментарии "вот такой код ошибки надо заменить на вот такой" оставлены без внимания
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 12:52

Вопрос(ы) Тёмычу:
1) Какое время жизни функтора анонимного метода? (т.е. метода, созданного конструкцией Delegate ... End Delegate)

Он убивается при выходе из процедуры, его создавшей или же после этого он продолжает жить?

2) Если анонимный метод убивается при выходе их функции-создателя, то что произойдёт при попытке вызвать этот метод через функтор извне?

Пример:

Код: Выделить всё
Module mod1
Public Delegate Sub AM()

Public MyFunctor as AM


Dim Foo as TestClass

     Public Sub Main()
           Set Foo = New TestClass             
           Foo.CreateAnonMethod      ' Создаётся анонметод и выводится сообщение. При выходе из метода CreateAnonMethod анонметод делегат ссылающийся на анонметод сбрасывается

           Call MyFunctor()              ' Что теперь???
     End sub

End Module

Class TestClass
    Public Sub CreateAnonMethod
          Set MyFunctor = AddressOf Delegate()
                  MsgBox "Я анонимный метод!"
          End Delegate

          Call MyFunctor() ' Должно появиться сообщение "Я анон метод"
    End Sub
End class



Во втором случае, т.е. в случае если он продолжает жить, как еге локальные переменные замыкаются (вернее на что?) при вызове изнве?



Код: Выделить всё

Module m1



    Public Delegate Sub CallbackDelegate(ByVal NewValue as long)
    Dim MyCachedDelegate as CallBackDelegate

      Public Sub GetInfoCallbackReciver(CallBack As CallbackDelegate)
              Set MyCachedDelegate  = CallBack
              Call Callback(2+8*Int(Rnd*4000))
      End Sub


      Public Sub Main
           Dim Foo as new TestClass2
           Foo.GetSomeInfo      ' Тут всё работает правильно.
           ' Анон-метод и делегат по прежнему живы.

           Call MyCachedDelegate (1234567) 
           ' Как это будет работать? На что будет замыкаться внутри-анон-методная переменная LocalDataVariable  ?
      End Sub
End Module

Class TestClass2
     Public Sub GetSomeInfo()
        Dim LocalDataVariable As Long

          GetInfoCallbackReciver AddressOf Delegate(ByVal NewVal as long)
                MsgBox "Я callback. Мне передали "+Cstr(newval)

                LocalDataVariable = NewVal ' LocalDataVariable замыкается на внешнюю внутре-методную локальную переменную.
                ' После вызова этого анонметода LocalDataVariable в GetSomeInfo изменится на NewVal.
          End Delegate

          MsgBox Cstr(LocalDataVariable) + " - новое значение. Анонметод действительно изменил нашу локальную переменную!"
     End Sub
End Class




2) Когда мы увидим вторую часть :) ?
Остальные вопросы будут заданы после получения ответов на эти.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Re: Мысленный эксперимент: ООП без классов

Сообщение tyomitch » 20.08.2007 (Пн) 19:42

1. демонстрируешь невнимательность ;-)

tyomitch писал(а):Сохранить внутри функтора ссылки на них тоже недостаточно: при возврате из внешнего метода все его локальные переменные уничтожатся, а экземпляры вложенных в него анонимных методов могут продолжать жить, если на них остались ссылки.


(остальные вопросы ещё не читал)
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 19:48

Далее следующий текст мне не нравится, скажем так.

Я хочу что-бы замыкания реализовывались уровнем ниже, и чтобы анонимный метод работал со стековым кадром функции-создателя.

Выносить локальные переменные в какой-то отдельный класс - это слишком громоздко.Или я чего-то не понимаю?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 19:58

Остальные вопросы из тех, что выше, уже можно не читать, так? :-)

Ответ на последний: нет, вся фишка и весь понт в возможности создать анонимный метод, который будет продолжать жить столько, сколько он нужен. Если это можно реализовать менее громоздко, то интересно узнать как.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 20:01

Дык, я бы ответил, знай я как оно должно работать после смерти метода-парента.

Замыкания должны исчезать и переменные должны становится собственными локальными переменными анон-метода?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 20:16

Хакер писал(а):Дык, я бы ответил, знай я как оно должно работать после смерти метода-парента.

Так же, как и до.

Как это будет реализовано -- как раз и пытаемся обсудить.

Хакер писал(а):Замыкания должны исчезать и переменные должны становится собственными локальными переменными анон-метода?

Не канает. Во-первых, хз что значит "переменные должны становится собственными локальными переменными". Код анонимного метода компилируется только один раз.

Во-вторых, как быть, когда несколько анонимных методов ссылаются на локальные переменные некого третьего метода, и этот третий метод завершился? Один анонимный метод должен ссылаться внутрь стека другого? Это бессмыслица: пока выполняется первый метод, значит второй не выполняется, и своего стекового кадра у него нет.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 20:33

Почему не канает? Допустим мы будем использовать в анон-методе для работы со стеком везде где это приемлемо адресацию через ebp.

Задача анон-метода лишь поместить в ebp нужное число. Если анон-метод вызывается таким образом, что где-то там выше в стеке находится кадр парента, то в ebp помещается адрес начала этого стекового кадра. Если же из парента был произведён выход, то в ebp нужно просто скопировать esp (и работать со своим собственным кадром).

Другое дело в способе определения того, что анон-метод вызывается из парента - ведь он может вызываться из некоторой функции А, которая вызывается из функции Б, которая вызвана из парента.


Можно для каждой функции держать в памяти своеобразный флаг, а именно - dword.

При входе в функцию парент туда копируется esp, при выходе этот дворд обнуляется.

Анон метод в таком случае если дворд обнулён помещает в ebp esp, а если там не ноль, помещает в ebp то что там находится.

Ы?

Во-вторых, как быть, когда несколько анонимных методов ссылаются на локальные переменные некого третьего метода, и этот третий метод завершился? Один анонимный метод должен ссылаться внутрь стека другого? Это бессмыслица: пока выполняется первый метод, значит второй не выполняется, и своего стекового кадра у него нет.


А одинаковые переменные нескольких анон-методов тоже должны замыкаться между собой (а не только с переменные метода-парента)?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 20:47

1. Интересно поглядеть, как ты сделаешь код, который иногда заглядывает в какой-то стековый кадр родительского метода (что, если он рекурсивный?), а иногда заглядывает в свой (после того, как родительский метод закончился).
При каждом обращении к локальным переменным будет ветвление?


2. Тоже должны.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 20:51

tyomitch
1) Пример на асме с синаксом FASM-а сгодится?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 20:54

Я его не знаю. Если это то GNU-тое убожество с AT&T-шным порядком аргументов, то не сгодится. Если это нечто иное, надеюсь разобраться.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 20:56

Если это то GNU-тое убожество с AT&T-шным порядком аргументов



Эээ... какой вообще порядок аргументов, и, хм..., какие ещё аргументы могут быть в асме?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 21:03

Ну например, вот фрагмент из моего /usr/src/sys/boot/i386/mbr/mbr.s: (выбран случайно)
Код: Выделить всё
main:           xorw %si,%si
                movw $partbl,%bx
                movb $0x4,%cl
main.1:         cmpb %ch,(%bx)
                je main.2
                jg err_pt
                testw %si,%si
                jnz err_pt
                movw %bx,%si
main.2:         addb $0x10,%bl
                loop main.1



Это не оно?
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 21:08

Ни в коем случае.

Вот пример (взят случайно, любителей оптимизации попрошу не ругаться на cmp вместо test и sub, где последнии были бы быстрее):

Код: Выделить всё
KrmClearAll:
        cmp     [KRMFinalized], 0
        je      KrmClearAllEnd
        push    ebx
        push    ebp

        mov     ebx, [KrmLFT]                   ; Помещаем в EBX адрес LFT
        sub     ebx, 4                          ; Уменьшаем его на 4
        mov     ecx, [KRMLFTSize]               ; Помещаем в ECX размер LFT

      KrmClearAllLoop:
        mov     eax, [ebx+ecx*4]                ; Читаем LFT
        cmp     [KRMKillerPtr], eax             ; Сравниваем содержимое ячейку с адресом киллера
        je      KrmClearAllSkipCell             ; Сохраняем ECX в стеке
        push    ecx

        push    eax                             ; Адрес роутера
        call    KrmReadRouterMagicSpot          ; Читаем MS роутера
        push    dword[esp]                      ; Глубина прохода
        push    eax                             ; Адрес JAT
        call    KrmDestroyKRM                   ; Разрушаем JATTree
        pop     ecx                             ; Восстанавливаем ECX
        mov     eax, [KRMKillerPtr]             ; Заполняем ячейку Killer-ом (адресом оного)
        mov     [ebx+ecx*4], eax

      KrmClearAllSkipCell:
        loop    KrmClearAllLoop                 ; Циклимся...

      KrmClearAllFinalizer:
        pop     ebp
        pop     ebx
      KrmClearAllEnd:
        ret     0d   

Взят случайно из открытого исходника.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 21:18

Т.е. отличие от традиционного синтаксиса только в квадратных скобках вокруг имён переменных?
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 21:22

Нет переменных в асме, Тёмыч, нет :)

Есть метки, объявления которых убираются, а упоминания которых заменяются на адрес.

Если что-то указано в квадратных скобках, то оно означает то, что расположено по адресу, если адрес расположен в скобках.

Иными словами
mov ecx, [eax] Поместит в ecx то, что содержится по адресу, который содержится в eax.

Никаких dword ptr писать не надо, потому что размер операндов определяется автоматически (а где не определяется - надо), а ptr вообще выброшено из синтаксиса фасма, ибо нафиг не надо.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 21:32

Кошмар, меня асму учат :shock:

В традиционном синтаксисе упоминание имени переменной (которая больше чем метка, ибо под неё выделяется память) обозначает содержимое этой переменной. А тут, видимо, это упоминание обозначает её адрес.

Пофиг. Разберусь, поди.

В качестве тестового примера для переложения на FASM предлагаю этот.

Код: Выделить всё
Sub foo(ByVal x As Long)
Dim i As Long
For i = 1 To 10
    (AddressOf Delegate(ByVal y As Long)
        RegisterSomewhere AddressOf Delegate() As Long
            Delegate = x + y
        End Delegate
    End Delegate)(i)
Next
End Sub


Смысл здесь в том, что внутри функции создаётся десять разных замыканий, ссылающихся на одну и ту же её переменную.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 21:52

В традиционном синтаксисе упоминание имени переменной (которая больше чем метка, ибо под неё выделяется память) обозначает содержимое этой переменной. А тут, видимо, это упоминание обозначает её адрес.


Это неправильно.

Это увеличивает разницe между асм-кодом и результатом его компиляции.

И что значит "традиционный синтаксис"? Синтаксис МАСМ-а небось?

В качестве тестового примера для переложения на FASM предлагаю этот.

Сначала скажи мне:
1) Что значит (AddressOf Delefate ... End Delegate)(i)? Вызов анонимного метода что-ли такой?

2) Что такое RegisterSomewhere ?

3) Каким образом это связано с житиём анон-метода после смерти парента?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 21:57

1) угу. (На каждый вызов создаётся новый стековый кадр.)
2) неизвестно коду что. Некая процедура, принимающая функтор.
3) сделай их так, чтобы они продолжали жить.
Последний раз редактировалось tyomitch 20.08.2007 (Пн) 22:19, всего редактировалось 1 раз.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 22:13

1) (Почему именно так?)
2) Ну ну мне то надо знать что, чтобы её написать.
3) Тогда давай возьмём один из моих примеров, он более соответсвует задаче.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 20.08.2007 (Пн) 22:24

1. Почему на каждый вызов функции создаётся новый стековый кадр, объяснять? (Лишнее условие убрал. Его выполнение или невыполнение не должно было отразиться на наблюдаемых эффектах.)
2.
Код: Выделить всё
Public Delegate ReturnsLong() As Long
Public Sub RegisterSomewhere(ByVal f As ReturnsLong)

3. Чем мой плох? Впрочем, ты волен постить что захочешь.
Изображение

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16036
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 20.08.2007 (Пн) 23:06

1. Не надо путать функцию и анонметод с замыканиями.
2. Что это?
3. Тем, что там нет вызова анонметода с замыканиями после выхода из парента.


Вот мой пример:

Код: Выделить всё

Class JaMaCa
       Delegate Sub Foo(ByVal z as long)
       
       Private MyDelegate As Foo

       Public Sub AnonCreator()
           Dim XIAN as long
                  XIAN = 23
                  Set MyDelegate  = AddressOf Delegate(byval zzz as long)
                                 XIAN = zzz + 1   ' Замыкание!
                                 EchoNumber XIAN ' Некий Sub, выводящий число
                  End Delegate
                  Me.DelegateCaller(77)
                  EchoNumber XIAN
       End SUb

        Public Sub DelegateCaller(Byval z_pass As Long)
                  call MyDelegate(z_pass)     
       End Sub

       Public Sub JaMaCa() ' Типа конструктор :)
              Me.AnonCreator()
              Me.DelegateCaller(2)
       End Sub


End Class

Dim ZuZuzu as New JaMaCa()


фасм представление:

Внимание: в контексте данной задачи я решил отказаться от классов с целью упростить код и убрать из него "лишнее" (передачу указателя на this и тд.). Делегат при таком раскладе превратился в просто указатель на функцию. Ну и методы класса скажем так у нас получились статическими (в том смысле что вызов происходит сразу, без применения vtbl).


Код: Выделить всё
section '.data' data readable writable
        flag_AnonCreator    0x00000000
        var_MyDelegate      0x00000000



section '.code' code readable executable
       

method_AnonCreator:
        mov     [flag_AnonCreator], esp         ; /// Установка "флага"

        mov     [var_MyDelegate], precompiled_AnonMethod1       ; Set MyDelegate = AddressOf ....

        push    77d
        call    method_DelegateCaller           ; Me.DelegateCaller(77)

        push    [esp-04]                        ; /// Кладём в стек локальную перем. XIAN
        call    EchoNumber                      ; EchoNumber XIAN

        mov     [flag_AnonCreator], 0           ; /// Снятие "флага"
        retn    0

method_DelegateCaller:
        push    [esp+04]                        ; /// Аргумент z_pass кладём в стек для вызова анон-метода
        call    [var_MyDelegate]                ; call MyDelegate(z_pass)
        retn    4d

method_JaMaCa:
        call    method_AnonCreator              ; Me.AnonCreator()

        push    2
        call    method_DelegateCaller           ; Me.DelegateCaller(2)
        retn    0

precompiled_AnonMethod1:
        push    ebp                             ; /// Сохраняем, ибо должны
        mov     ebp, esp
        add     ebp, 4                          ; /// Теперь в EBP то же что было в ESP при входе в анон-метод

        cmp     0, [flag_AnonCreator]
        jne     SkipStackFrameRelocation
        mov     ebp, [flag_AnonCreator]
      SkipStackFrameRelocation:
        ; А теперь пошёл сам код анон-метода:
        mov     eax, [esp+08]                   ; /// Помещаем в eax аргумент zzz
        inc     eax                             ; zzz + 1
        mov     [ebp-04], eax                   ; XIAN = (zzz + 1)  ---> mov XIAN, (zzz+1) ---> mov [ebp-04], eax

        push    [ebp-04]                        ; /// Здесь следовало бы написать push eax. Но я специально пишу так, как пишу :)
        call    EchoNumber                      ; /// Некий метод, который сообщает переданное число
        pop    ebp
        retn    4d
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

tyomitch
Пользователь #1352
Пользователь #1352
Аватара пользователя
 
Сообщения: 12822
Зарегистрирован: 20.10.2002 (Вс) 17:02
Откуда: חיפה

Сообщение tyomitch » 21.08.2007 (Вт) 7:43

1. Какая (в данном случае) разница?
2. Это объявление
3. Какая разница? Вызов всегда происходит одинаково.

Ты так пишешь, как будто я собираюсь скомпилить твой код и гонять какие-то тесты. Нет, я его собираюсь просто читать.


По поводу твоего примера мне представляется очевидным, что по второму вызову AnonCreator (хотя бы из JaMaCa) испортится кадр всех оставшихся с первого раза анонимных методов.

Ещё мне кажется, что в строчке "mov [ebp-04], eax" обращение идёт не к XIAN, а к копии старого ebp, положенной в стек первой командой ;-)
Изображение

След.

Вернуться в Tyomitch

Кто сейчас на конференции

Сейчас этот форум просматривают: Google-бот и гости: 1

    TopList