<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      本人在OOP方面剛剛入門,只是看過一些國外這方面的好材料,才萌生了寫本文的念頭。希望能夠起到拋磚引玉的作用,引出高手們的批評和建議。

      OOP和面向過程都是編程中的思想,用學術一些的話是paradigm。曾經有人說過,既然cfront生成的是C代碼,那么用C本身乃至匯編都可以實現OOP,只是太多東西需要自己手工來完成。確實是這樣的,面向過程早就用在匯編設計中了,OOP也早就和匯編有了交匯點(95年之前,TASM就引入了OOP的概念)。只是匯編實現OOP是沒有形式上的,無法提供C++這樣的Strong-typed和其他安全保證(比如存取權限)。封裝只是一種概念上的,自覺遵守的。

      OOP有幾個關鍵,據我的粗淺理解即為:封裝性,繼承與多態。具體表現就是把數據和操作數據的函數放在一起,數據放在對象中,提供接口實現存取。繼承性實現了語義或者實現的繼承,同時體現在概念層次與代碼重用兩個方面。多態則是利用指針實現使用pointer或reference來實現同一函數在不同繼承類中的多態表現。

      OOP的對象模型有好幾種實現方式,在《inside C++ object model》中有極其詳盡的敘述:

      • 1.只把數據放在對象中,而通過name mangling技術把member-function與class關聯起來。
      • 2.單表模型,把member function的pointer放入到單獨的一個表格,把表格的入口地址放入對象中(一個類對應一個表格)。這在C++中表現為Vtbl與Vptr,這種模型實現了運行時的動態靈活性,雖然多了兩次dereference。
      • 3.雙表模型,把數據與函數分列在兩個表格中,然后把兩個表格的入口地址存放在對象中,使得單個對象有了固定的大小。
      • 4.簡單模型,這個是匯編實做的時候用的模型。就是對象中即保存了數據也保存了函數地址。無論是TASM還是MASM,都是這么做的。

      從效能上來說,C++的做法是最優的。匯編使用第四種是迫不得已,是為了實現的簡單性。一定程度上與匯編的高效的精神違背。

      TASM已經不常用了,其OOP的做法和MASM的做法也是類似的。這里主要討論MASM的OOP做法。作者是NaN 和Thomas Bleeker。其實現的辦法是用宏定義來達到本來應該是編譯器做的幕后工作。其中的宏的技巧很多。但是最終的使用是挺簡單的。宏的定義放在一個OBJECTS.INC的文件中,asm文件包含這個inc就能使用這個object model。

      雖然宏做得很精巧,但是畢竟MASM缺少支持OOP的語法特性,在使用的很多方面都有麻煩或者在空間時間上有代價。比如覆蓋基類的虛函數必須每次手工的完成。也就是繼承的層次中所有父類以上的被覆蓋的虛函數都需要在子類中手工完成。雖然是有這樣那樣的缺點,但是OOP還是給匯編帶來了不少好處。比如:

      • 1.匯編更好的和COM,C++這樣的面向對象領域的東西互動。已經有用匯編+OOP調用com的例子。如果用匯編+OOP來寫com將可以產生適合高速度和小尺寸的組件。
      • 2.擴大了匯編能夠解決的問題范圍,使得匯編程序更加容易管理和合作編寫。這個object model的作者就用匯編+OOP寫了一個基于神經網絡的手寫字母識別的程序,不到200k(其中大部分是圖象文件占用的空間)。

      使用

      定義一個基類的辦法。

      ;準備好函數原型
         Shape_Init    PROTO  :DWORD
         Shap_destructorPto    TYPEDEF  PROTO  :DWORD 
         Shap_getAreaPto    TYPEDEF  PROTO  :DWORD
         Shap_setColorPto    TYPEDEF  PROTO  :DWORD, :DWORD
      
      ;實際上就是STRUC的定義 CLASS Shape, Shap CMETHOD destructor CMETHOD getArea CMETHOD setColor Color dd ? Shape ENDS
      .data ;初始化 BEGIN_INIT dd offset Shap_destructor_Funct dd offset Shap_getArea_Funct dd offset Shap_setColor_Funct dd NULL END_INIT
      .code Shape_Init PROC uses edi esi lpTHIS:DWORD ;實際調用初始化 SET_CLASS Shape ;把edi assmue 為Shape類型 SetObject edi, Shape ;額外定義的DPrint宏,不用細究 DPrint "Shape Created (Code in Shape.asm)" ;取消assmue ReleaseObject edi ret Shape_Init ENDP Shap_destructor_Funct PROC uses edi lpTHIS:DWORD SetObject edi, Shape DPrint "Shape Destroyed (Code in Shape.asm)" ReleaseObject edi ret Shap_destructor_Funct ENDP Shap_setColor_Funct PROC uses edi lpTHIS:DWORD, DATA:DWORD SetObject edi, Shape mov eax, DATA mov [edi].Color, eax DPrint "Shape Color Set!! (Code in Shape.asm)" ReleaseObject edi ret Shap_setColor_Funct ENDP Shap_getArea_Funct PROC uses edi lpTHIS:DWORD SetObject edi, Shape DPrint " " DPrint " SuperClassing!!!!! This allows code re-use if you use this method!!" DPrint " Shape's getArea Method! (Code in Shape.asm)" mov eax, [edi].Color DPrint " Called from Shape.getArea, (Code in Shape.asm)" DPrintValH eax, " This objects color val is" DPrint " " ReleaseObject edi ret Shap_getArea_Funct ENDP

      繼承這個類

      include Shape.asm  ; Inherited class info file
         Circle_Init    PROTO  :DWORD
         Circ_destructorPto    TYPEDEF  PROTO  :DWORD 
         Circ_setRadiusPto    TYPEDEF  PROTO  :DWORD, :DWORD
         Circ_getAreaPto  TYPEDEF PROTO :DWORD
         
      CLASS Circle, Circ ;繼承原有的數據和函數 Shape <> ; Inherited Class CMETHOD setRadius Radius dd ? Circle ENDS
      .data BEGIN_INIT dd offset Circ_destructor_Funct dd offset Circ_setRadius_Funct dd NULL END_INIT
      .code Circle_Init PROC uses edi esi lpTHIS:DWORD ;初始化并實現繼承 SET_CLASS Circle INHERITS Shape SetObject edi, Circle ;相當于構造函數重置vptr OVERRIDE getArea, CirleAreaProc DPrint "Circle Created (Code in Circle.asm)" ReleaseObject edi ret Circle_Init ENDP Circ_destructor_Funct PROC uses edi lpTHIS:DWORD SetObject edi, Circle DPrint "Circle Destroyed (Code in Circle.asm)" ;實現了基類函數的調用 SUPER destructor ReleaseObject edi ret Circ_destructor_Funct ENDP Circ_setRadius_Funct PROC uses edi lpTHIS:DWORD, DATA:DWORD SetObject edi, Circle mov eax, DATA mov [edi].Radius, eax DPrint "Circle Radius Set (Code in Circle.asm)" ReleaseObject edi ret Circ_setRadius_Funct ENDP CirleAreaProc PROC uses edi lpTHIS:DWORD LOCAL TEMP SetObject edi, Circle SUPER getArea mov eax, [edi].Radius mov TEMP, eax finit fild TEMP fimul TEMP fldpi fmul fistp TEMP mov eax, TEMP DPrint "Circle Area (integer Rounded) (Code in Circle.asm)" ReleaseObject edi ret CirleAreaProc ENDP

      根據類來生成對象,并使用

      DEBUGC equ 1
      .586
      .model flat,stdcall
      option casemap:none
      include \masm32\include\windows.inc
      include \masm32\include\masm32.inc
      include \masm32\include\kernel32.inc
      include \masm32\include\user32.inc
      includelib \masm32\lib\kernel32.lib
      includelib \masm32\lib\user32.lib
      includelib \masm32\lib\masm32.lib
      include Dmacros.inc
      include Objects.inc
      include Circle.asm
      .data
      .data?
      hCircle dd ?
      .code
      start:
        ; Recuse all inherited constructors.. and do all inits
        DPrint " "
        DPrint " >>> main.asm <<< [ mov hCircle, $NEW( Circle ) ]"
        mov hCircle, $NEW( Circle )
        DPrint " "
        DPrint " >>> main.asm <<< [ METHOD hCircle, Circle, setColor, 7 ]"
        METHOD hCircle, Circle, setColor, 7
        
        DPrint " "
        DPrint " >>> main.asm <<< [ METHOD hCircle, Circle, setRadius, 2 ]"
        METHOD hCircle, Circle, setRadius, 2  
        
        DPrint " "
        DPrint " ------------ TEST POLYMORPHIC METHOD hCircle.getArea ------------- "
        DPrint " "
        DPrint " >>> main.asm <<< [ DPrintValD $EAX( hCircle, Circle, getArea ) , 'Area of hCircle' ]"
        DPrintValD $EAX( hCircle, Circle, getArea ) , "Area of hCircle"
        DPrint " "
        DPrint " ------------ TEST POLYMORPHIC METHOD hCircle.getArea ------------- "
        DPrint " "
        DPrint " >>> main.asm <<< [   DPrintValD $EAX( hCircle, Shape, getArea ) , 'Area of hCircle' ]"
        DPrint " Typing calling this Ojbect Instance as a SHAPE type only! This is the true value"
        DPrint " of Polymorphism.  We dont need to know its a Circle object in order to get the"
        DPrint " proper area of this instance object, that is inherited from Shape."
        DPrint " "
        DPrintValD $EAX( hCircle, Shape, getArea ) , "Area of hCircle"
        DPrint " "
        
        
        DPrint " "
        DPrint " >>> main.asm <<< [ DESTROY hCircle ]"
        DESTROY hCircle
        DPrint " "
        DPrint " " 
        DPrint " NOTE: superclassing here, as each destructor call's the SUPER destructor"
        DPrint "       To properly clean up after each class.  To see SUPER classing in"
        DPrint "       in the Polymorphic getArea Function.  Uncomment the SUPER code in"
        DPrint "       CircleAreaProc, and re-compile"
      
           call ExitProcess
      end start
      

      看起來挺雜亂的,其實還是挺整齊的。由四部分組成。

      • 第一部分是各個成員函數的聲明。特別的一定要有一個“類名_Init”的函數,這個函數是類的構造函數,名字就是這個,不能改動。
      • 第二部分是由class引導的函數聲明,其實就是定一個STRUC,也就是結構體。其中通過內含基類的定義來達到結構上的繼承。(數據上的繼承在構造函數中調用SET_CLASS完成)。
      • 第三部分是放在.data中的初始化序列(BEGIN_INIT,END_INIT)。相當于C++的vtbl,但又包括了對象的數據成員的初始值。
      • 第四部分是各個成員函數的實現。特別的是構造函數中要調用的SET_CLASS和有可能調用的OVERRIDE,完成了數據的繼承和虛函數的改寫。

      實際使用中參考一些已經有的例子就可以蠻舒暢的使用了。確實可以帶來很大的方便。


      原理

      所有的奧秘都在Object.inc中,其中定義了如下的宏

      ; --=====================================================================================--
      ; MACRO LIST INDEX:
      ; --=====================================================================================--
      ;   NEWOBJECT       創建新的對象實體
      ;   METHOD          調用實體中的函數
      ;   DESTROY         destroy 對象 (MUST)
      ;   SetObject       把寄存器中的指針“認為”某種結構的指針
      ;   ReleaseObject   取消這種“認為”
      ;   OVERRIDE        改寫表格中函數的地址,實現多態
      ;   SET_CLASS       實現初始化,如有必要實現繼承 (MUST)
      ;   SUPER           調用基類函數的支持
      ;
      ;   $EAX()           Accelerated METHOD, returns in eax
      ;   $EBX()           Accelerated METHOD, returns in ebx
      ;   $ESI()           Accelerated METHOD, returns in esi
      ;   $EDI()           Accelerated METHOD, returns in edi
      ;   $NEW()           Accelerated NEWOBJECT, returns in eax
      ;   $SUPER()         Accelerated SUPER, returns in eax
      ;   $DESTROY()       Accelerated DESTROY, returns in eax
      ;   $invoke()        Accelerated invoke, returns in eax
      ;
      ;   BEGIN_INIT      在數據段中標記初始化的信息 (MUST)
      ;   END_INIT        表明標記結束 (MUST)
      ;
      ;   CLASS           即為STRUCT (MUST)
      ;   SET_INTERFACE   To Declair Abbreviated Interface and Abv Name (MUST)
      ;   CMETHOD         聲明類或者接口中的函數
      ;
      ; --=====================================================================================--
      

      宏的數量也不是很多,但是的確完成了編譯器為我們完成的幕后工作。我只列出宏展開前后的代碼,并加以解釋。對于宏的具體實現由于牽涉到諸多語法和技巧,不方便詳細講解(其實我也是剛剛通過查手冊,一點點讀懂的)。

      先來看CLASS吧,這個是想當然的入口部分。

      其實很簡單就是把Class換成STRUC。

      CLASS Shape, Shap
       CMETHOD destructor
       CMETHOD getArea
       CMETHOD setColor
       Color     dd    ?
      Shape ENDS
      

      替換之后就是

      Shape STRUC
       CMETHOD destructor
       CMETHOD getArea
       CMETHOD setColor
       Color     dd    ?
      Shape ENDS
      

      很自然,看看CMETHOD是怎么做的

      CMETHOD destructor
      

      就變成了

      destructor PTR Circ_destructorPto ?
      

      整個就展開為:

      Shape STRUC
        destructor PTR Circ_destructorPto ?
        getArea PTR Circ_getAreaPto ?
        setColor PTR Circ_setColor ?
        Color     dd    ?
      Shape ENDS
      

      ^_^,結構體中就是函數指針和數據嘛。然后,線索就斷了。光這樣定義一個結構是肯定不行的。那么就從對象的產生開始吧,new是怎么做的。

      NEWOBJECT Circle
      

      -->

      invoke GetProcessHeap
      invoke HeapAlloc, eax, NULL, SIZEOF Circle
      push   eax
      invoke Circle_Init, eax
      pop    eax
      

      這里顯示了一個很明顯的缺陷,就是一定要在win32下使用,因為win32api的使用。可以把api替換成一個外部的函數。然后放在不同的平臺上使用只要改變這個動態分配內存的函數就可以了。

      產生的代碼很樸實,就是分配內存,然后調用對象的構造函數。這里,強制的要求類的構造函數要以“類名_Init”的形式。雖然不是什么大的限制,但是也不是很爽。這樣做也是有道理的,通過在編寫上對接上名字可以避免用指針這樣的東西來實現靈活性所帶來的overhead,下面可以看到析構函數用了指針的形式,這是因為這里默認了virtual desturctor。

      好,下面前進到構造函數,我們看構造函數是怎么寫的:

      Shape_Init  PROC uses edi esi lpTHIS:DWORD
         SET_CLASS Shape
         SetObject edi, Shape
           DPrint "Shape Created (Code in Shape.asm)"
         ReleaseObject edi
         ret
      Shape_Init ENDP
      

      lpTHIS應該不會陌生,就是指向對象的指針。這里一個對象也就是一個sturct啦。第一行就是關鍵所在,SET_CLASS是最麻煩和富有技巧的一個宏。我們來看看是怎么做的

      SET_CLASS Shape
      

      -->

      push    esi
      push    edi
      cld   
      mov  esi, offset @InitValLabel
      mov  edi, lpTHIS
      mov  ecx, @InitValSizeLabel
      shr  ecx, 2
      rep  movsd
      mov  ecx, @InitValSizeLabel
      and  ecx, 3
      rep  movsb
      pop  edi
      pop  esi
      

      push和pop是很普通的保存現場的做法。而mov esi, offset @InitValLabel則是和后面的BEGIN_INIT有關。offset @InitValLabel也就是BEGIN_INIT所標記的地址。這一段程序其實沒有做什么特別的事情。也就是把BEGIN_INIT和END_INIT之間的初始化的數據賦給剛剛new出來的對象。lpTHIS就是這個對象的地址。由于SET_CLASS總是假定你在構造函數中調用它,所以lpTHIS當然是存在的(作為構造函數的參數)。cld, rep movvsd 等都是匯編的快速搬移數據的技巧。查一下手冊就知道是干什么的了。也就是一開始盡量一個dword一個dword的搬移,然后就一個byte一個byte的移,直到全部都搬過去了。

      如果帶上了繼承,則要麻煩許多

      SET_CLASS Circle INHERITS Shape
      

      -->

      push    esi
      push    edi
      cld
      mov  edi, lpTHIS
      mov  esi, offset @InitValLabel
      mov     eax, [esi]
      mov     [edi], eax
      add     esi, 4     
      add     edi, Inher
      mov  ecx, (@InitValSizeLabel - 4)
      shr  ecx, 2
      rep  movsd
      mov  ecx, (@InitValSizeLabel - 4)
      and  ecx, 3
      rep  movsb
      pop  edi
      pop  esi
      

      由于繼承了,所以要重置析構函數。mov eax, [esi]和mov [edi], eax做了這樣的工作。而由于析構函數的地址已經改變了,所以只需要也只能繼承后面的數據成員包括虛函數的指針

      接下來是對象的destroy

      DESTROY hCircle
      

      -->

      mov eax, hCircle
      push eax
      call dword ptr [hCircle]
                
      push eax
      invoke GetProcessHeap
      invoke HeapFree, eax, NULL, hCircle
      pop eax
      

      由于析構函數的地址是對象(結構體)的第一個成員,所以call就是調用析構函數。調用了之后就用win32api把申請的內存釋放掉

      接下來是析構函數

      Circ_destructor_Funct  PROC uses edi lpTHIS:DWORD 
         SetObject edi, Circle
           DPrint "Circle Destroyed (Code in Circle.asm)"
           
           SUPER destructor
         ReleaseObject edi
         ret
      Circ_destructor_Funct  ENDP
      

      這個是shape的繼承類circle的析構函數。里面有一個SUPER,實現了調用基類中的函數。我們來繼續看它的實現。

      SUPER destructor
      

      -->

      invoke Circ_destructorPto  PTR [ (INHER_initdata+INHER.MethodName) ], lpTHIS
      

      Circ_destructorPto指定這個地址的類型是一個什么樣的函數。INHER是宏內部的一個全局的東西,表示該類的基類名稱。INHER_initdata+INHER.MethodName的結構就是這個類在基類中的實際地址。

      剩下的就是實際的使用對象中的函數了(你是“無權”操作對象中的數據的,雖然是概念上的。實際上你可以肆意的破壞這里體現的OOP思想。因為匯編不提供這樣的保護)。

      METHOD hCircle, Shape, getArea
      

      -->

      mov edx, hCircle
      invoke (Shape PTR [edx]).getArea, edx
      

      收獲的季節了。這一句體現了多態的思想。hCircle指向的是一個Circle類的對象,但是調用的時候解釋為Shape類。自己去理解吧。哪里體現了多態。


      我的看法

      從全局來看這個對象模型,我們可以發現是這樣的。

      • 對象數據和虛函數指針放在同一個表格中
      • 所有的函數都是虛的
      • 繼承類改寫基類的虛函數需要在初始化數據之后手工完成(構造函數中)
      • 僅提供對上一層基類中被改寫的虛函數的訪問
      • 內存的分配和釋放使用win32api

      OOP的三個特性的支持,如下

      • 封裝性:并沒有提供對于其中數據的特別保護(沒有Private)。數據和函數指針置于同一個結構體中成為一個對象。訪問數據通過提供的接口完全靠自覺。
      • 繼承:通過結構體定義的嵌套(定義中包含已經定義的結構體),完成結構上的繼承。通過SET_CLASS完成數據意義上的繼承。所有的繼承都是Public的。
      • 多態:多態的狹義理解是對于同一個函數的調用將有不同的行為。我們通過以下比較觀察,為什么這個對象模型支持了多態(因為它支持了派生類對于基類函數的改寫)。
      class Shape
      {
       virtual float getArea();
       ……
      };
      class Cicle: public Shape
      {
       float getArea();
       ……
      };
      

      當你通過對象指針調用一個對象中的虛函數的時候。其實你在編譯的時候已經指定了該指針的類型。比如:

      float getArea(Shape* shp)
      {
       return shp->getArea();
      }
      

      所以,編譯器可以通過查詢編譯時的信息來確定你所調用的函數在vtbl中的索引位置。然后這個調用就會被一個查詢vtbl,然后call所代替。而運行的時候,傳來的指針shp并不一定就是Shape類型,而是他的繼承類型。這樣兩個類的vtbl內容可能不一樣(派生類改寫了其中的某些slot的地址)。所以,這樣就可以實現在不知道派生類是什么的情況下調用派生類的函數。奧秘就在于派生類和基類都把各自的實現版本放在了vtbl的相同位置。編譯期確定了位置,運行時確定了該位置的內容。

      而這個匯編版的object model呢?其實差不多。mov edx, hCircle和 invoke (Shape PTR [edx]).getArea, edx就是一個多態的調用。hCircle實際指向一個Circle類型的對象(在這里對象即有數據又承擔了vtbl的任務)。而調用的時候設計是把這個指針作為Shape類性解釋的。也就是按照shape類型中getArea所在的index來調用。相同的index索引到不同的函數,多態就產生了。


      可能的改進

      關于虛函數

      其實說實話,這個對象模式做得實在是很不錯,將宏的功能發揮到了極致。不過它強制的要求所有的類都有一個virtual destructor和所有的函數都是virtual。在宏的能力所及的范圍內,已經將定義和調用都做得盡可能的簡單,使用起來也的確有賞心悅目的感覺。不過我覺得不要把所有的函數都放在對象中(即強制性的作為虛函數),那樣會增加一定的成本。

      C++把非虛的成員函數看作是普通的函數,放在對象之外。其實我覺得這個對象模型也可以采用。而現有的強制性的將一些成員函數原型名稱限定為“類型名_函數名Pto”,不如提供一個宏來這么做好了。

      我的建議,將部分函數放入對象中(就是用CMETHOD來聲明于類中)。而其他則不放入,只是寫在同一個文件中。然后用METHOD調用對象的member-function時,它會在匯編時決定這個member-function是否存在于虛函數表(也就是對象本身存放的一系列函數指針)中。如果不是則按照普通函數的一樣調用。如果是則按照現在這個模式調用。

      METHOD這個宏也是編寫得出來的。在SUPER這個宏中就有檢驗該method是否在基類中第一次出現(而且還實現了檢驗是否是上一層而不是多層基類的檢驗)。這么METHOD也可以檢驗調用的方法是否在class中出現過,然后分別使用不同的函數調用辦法。

      關于SUPER

      這個model中只能調用上一層(也就是父類的)的被覆蓋的函數,對于上上一層的被覆蓋的函數則無法SUPER了。其中的障礙在于無法知道那個函數是在哪個類型中第一次出現。我想如果手工提供這個類名,則可以SUPER任意層次的被覆蓋了的函數。像這樣:

      SUPER getArea

      SUPER getArea, Shape

      沒有提供具體的類名,則認為是SUPER上一層的,否則使用具體的類名來SUPER。SUPER的奧秘也就是查詢放在.data段的“類型名_initdata”中數據來達到“恢復”被改寫的函數的功能。

      隨便講一句,TASM中的TABLE卻可以彌補OVERRIDE的缺陷,是一種更好的STRUC。不過那是Borland加強語法的結果。無論如何Thomas做到這一步也算是相當厲害。


      鏈接

      本文中所提到的Object Model的出處
      posted on 2004-05-24 19:07  taowen  閱讀(1994)  評論(0)    收藏  舉報
      主站蜘蛛池模板: AV无码不卡一区二区三区| 亚洲乱码中文字幕小综合| 日韩欧美亚洲综合久久| av老司机亚洲精品天堂| 日韩熟女精品一区二区三区| 国产精品久久久久精品日日| 性男女做视频观看网站| 国产一级小视频| 高潮videossex潮喷| 精品亚洲女同一区二区| 丁香五月天综合缴情网| 国产女人喷潮视频免费| 女同另类激情在线三区| 夜夜躁狠狠躁日日躁| 免费AV片在线观看网址| 欧美性色黄大片| 国产亚洲一本大道中文在线| 国产高清精品在线一区二区| 国产精品毛片av999999| 一亚洲一区二区中文字幕| 黄男女激情一区二区三区| 久久久久国产一级毛片高清版A | 国产精品人成在线播放蜜臀| 亚洲色最新高清AV网站| 无遮挡高潮国产免费观看| 日本少妇自慰免费完整版| 久久99精品国产麻豆婷婷| 少妇人妻偷人精品无码视频新浪 | 熟女少妇精品一区二区| 国产肥臀视频一区二区三区| 91亚洲国产三上悠亚在线播放| 人妻在线无码一区二区三区| 亚洲综合伊人五月天中文| 亚洲精品自拍在线视频| 久久久久四虎精品免费入口| 午夜成人性爽爽免费视频| 无码人妻丰满熟妇区96| 日韩黄色av一区二区三区 | 极品尤物被啪到呻吟喷水| 成人午夜av在线播放| 日韩乱码人妻无码中文字幕视频|