亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频

蟲蟲首頁| 資源下載| 資源專輯| 精品軟件
登錄| 注冊

您現在的位置是:首頁 > 技術閱讀 >  被知乎大佬嘲諷后的一個月,我重新研究了一下內聯函數

被知乎大佬嘲諷后的一個月,我重新研究了一下內聯函數

時間:2024-02-10

前言




這絕不僅僅是一篇講內聯意義的文章

參考我的學習過程,可能對你的知識整合有很大幫助




之前寫了一篇總結c++面試的文章,被大佬糾出來很多關于內聯的問題與錯誤。抱著不誤導別人的態度(也因為上篇文章承諾要給大家深入分析一下內聯函數),我在最近的一個月里抽了很多時間去重新研究inline,確實學到了很多以前不了解的知識。學習么~就是一個不斷打破之前認知并重構知識的過程,每個人都是從一個什么都不懂的菜鳥逐漸成長為一個大牛的。


在這篇文章里,我會由淺入深的分析不同階段的我對內聯函數的認識,重構我的知識體系。即使你之前對inline不了解,也可以看得懂這篇文章。


由于篇幅比較長,會分成上下兩個部分。另外,文中會有很多引用的參考鏈接,我會統一放到文末的位置。這次我也重新的對文章做了排版,方便大家閱讀。(不過由于我公眾號開的較晚,開啟評論功能是遙遙無期了)



01:菜鳥階段


上大學第一次接觸C++,然后了解到了內聯函數。啥是內聯函數?簡單理解就是編譯時把函數的定義替換到調用的位置。


inline int Add(int a, int b)
{
  return a + b;
}
int main()
{
  int num1 = 1;
  int num2 = 2;
  int myNum = Add(num1, num2);
}
//這樣的代碼內聯之后大概就是
int main()
{
  int num1 = 1;
  int num2 = 2;
  int myNum = num1 + num2;
}


好的,感覺好像還挺簡單的。啥?你問我啥是編譯?嗯。。編譯就是把你的代碼通過編譯器分析一下然后轉換成計算機能直接讀懂的語言(匯編),最后生成一個可執行的程序(或可被調用的庫)。


當然,我這么解釋有點不太權威,咱們再看看維基百科關于內聯函數的定義:

在計算機科學中,內聯函數(有時稱作在線函數或編譯時期展開函數)是一種編程語言結構,用來建議編譯器對一些特殊函數進行內聯擴展(有時稱作在線擴展);也就是說建議編譯器將指定的函數體插入并取代每一處調用該函數的地方(上下文),從而節省了每次調用函數帶來的額外時間開支。但在選擇使用內聯函數時,必須在程序占用空間和程序執行效率之間進行權衡,因為過多的比較復雜的函數進行內聯擴展將帶來很大的存儲資源開支?!緟⒖?:內聯函數維基百科】


那么內聯函數有什么有點呢?當然是減少函數調用帶來的開銷了,幾乎每本C++入門書籍、百科以及博客都是這么說的。不過,什么是函數調用開銷?額,反正調用函數肯定要消耗CPU運算吧,肯定也有內存參與,肯定有開銷,嗯。

另外,我還從書上了解一些相關的知識,如直接在類的頭文件里面定義的函數都是自動內聯的(并不對),內聯相比宏定義有類型檢查、可支持類的訪問控制等優點。



這時候的我知道的專業名詞有:匯編、編譯、內聯、CPU、函數調用、內存地址,但是他們之間的關系幾乎是一頭霧水了。就如下圖一樣,


02:初識階段


之前總是說減少函數調用開銷,那么這個調用開銷到底是指什么?這時候的我發現有一些面試里面會問到這個問題,所以還真有必要理解一下了。


我們常說,C語言程序內存分為常量區、代碼區、靜態全局區、棧區、堆區。當我們的程序運行時,我們的編譯后的二進制程序這個二進制程序的分布格式差不多就是前面說的那幾個區,里面會有各種匯編命令)就會被放到操作系統的內存里面,函數代碼段被放在所謂的代碼區,局部變量與函數參數被放在棧區。函數調用就發生在棧區里面,每次調用的時候會把當前函數的相關內容壓入到棧里面處理寄存器相關的數據信息(所謂沒有地址的右值一般就是通過寄存器存儲的數據);然后,調用地址指向我們要執行的函數位置,開始處理函數內部的指令進行計算,當函數執行結束后,要彈出相關數據,處理棧內數據以及寄存器數據?!緟⒖?:淺談C/C++堆棧指引——C/C++堆棧很強大】


這個過程也就是所謂的“函數調用開銷”。

到現在為止,我們不妨先總結一下消除函數調用的直接好處【參考3:Inline expansion 】:

1.它消除了函數調用過程中所需的各種指令:包括在堆?;蚣拇嫫髦蟹胖脜?,調用函數指令,返回函數過程,獲取返回值,從堆棧中刪除參數并恢復寄存器等。

2.由于不需要寄存器來傳遞參數,因此減少了寄存器溢出的概率。當使用引用調用(或通過地址調用或通過共享調用)時,它消除了必須傳遞引用然后取消引用它們。


當然缺點我們也應該了解,使用不當的話就會造成代碼膨脹(也就是生成的可執行程序會變大);影響cache對數據的命中;如果你設計了一個函數庫,調用你的內聯函數還會造成客戶代碼的重新編譯。

一般一級高速緩存里面會分為指令緩存(instruction cache)以及數據緩存(data cache),inline的使用不當對二者都可能造成影響。首先,過多的內聯代碼會使原來本可以存儲到ICache的指令分散,導致指令緩存的命中降低,從內存取數據會嚴重影響效率。其次,inline會導致代碼膨脹,增加可執行程序(動態庫、靜態庫)體積,造成額外的換頁行為,進而可能會導致數據緩存的命中率降低。


上面說的缺點還比較抽象,很多情況好像都可以接受。而還有一些特定情況,內聯將會造成很嚴重的后果,如遞歸函數的內聯可能造成代碼的無限inline循環。所以編譯器在這些特殊情況下會拒絕內聯,常見的包括虛調用,函數體積過大,有遞歸,可變數目參數,通過函數指針調用,調用者異常類型不同,declspec宏、使用alloca、使用setjump等。不過這些情況編譯器也并不是一定會拒絕,虛調用在某些情況下就可以被內聯,會在第三部分細說。


這時候,我認識到,其實內聯inline只是建議性的關鍵字,編譯器并不一定會聽你的,畢竟他比你更了解你的代碼編譯后是什么樣子的,而所謂的內聯也不單單是指inline這個關鍵字了,他本質上是一種編譯器的優化方式。另外,在windows上平臺我還經常能看到forceinline【參考4:MS Doc】(GCC上的【always_inline】)這樣的關鍵字,字面意思是強制內聯。不過經過查閱,發現一般只是對代碼體積不做限制了,或者說在Debug模式(不不開啟優化的情況)下也會盡量按照開發者的意愿去內聯。無論如何,最終的決定權還是交給編譯器去處理。


在這個階段的學習過程中,我發現想理解程序的編譯與運行,還不得不去看看程序的反匯編代碼,看看編譯器編譯后的代碼是什么樣子的。畢竟很多時候,我們需要親自手動操作才能真正的理解其中的原理。


雖然我上學時很討厭這門課,但是我發現想大概看懂反匯編代碼,并不需要非常完善的匯編知識,只要把常見的一些命令記住并理解就行了?!緟⒖?:手把手教你棧溢出從入門到放棄 】

還是前面那段代碼,測試在VS2017下的匯編代碼(方法參考上圖,代碼主要看紅色部分)


inline int Add(int a, int b)
{
   return a + b;
}


int main()
{
   int num1 = 1;
   int num2 = 2;
   int myNum = Add(num1, num2);
}


//Debug模式下無內聯優化的匯編代碼,需要跳到Add函數的地址去執行計算
int main()
{
//前面匯編代碼省略

01232547  mov         eax,0CCCCCCCCh  
0123254C  rep stos    dword ptr es:[edi]  
0123254E  mov         ecx,offset _5BD3FBCE_consoleapplication2.cpp (01247008h)  
01232553  call        @__CheckForDebuggerJustMyCode@4 (0123142Eh)


int num1 = 1;

01232558  mov         dword ptr [num1],1


int num2 = 2;

0123255F  mov         dword ptr [num2],2


int myNum = Add(num1, num2);

01232566  mov         eax,dword ptr [num2]  
01232569  push        eax  
0123256A  mov         ecx,dword ptr [num1]  
0123256D  push        ecx  
0123256E  call        Add (01231726h)  
01232573  add         esp,8  
01232576  mov         dword ptr [myNum],eax  

}


int Add(int a, int b)
{
//前面匯編代碼省略

00891E67  mov         eax,0CCCCCCCCh  
00891E6C  rep stos    dword ptr es:[edi]  
00891E6E  mov         ecx,offset _5BD3FBCE_consoleapplication2.cpp (08A7008h)  
00891E73  call        @__CheckForDebuggerJustMyCode@4 (089142Eh)  


 return a + b;
00891E78  mov         eax,dword ptr [a]  
00891E7B  add         eax,dword ptr [b]  

}

//Debug模式下開啟內聯(/Ob2,參考上圖)后的匯編代碼,無需跳轉到Add函數的位置,直接優化計算

int main()
{
//前面匯編代碼省略

00F41F67  mov         eax,0CCCCCCCCh  
00F41F6C  rep stos    dword ptr es:[edi]  
00F41F6E  mov         ecx,offset _5BD3FBCE_consoleapplication2.cpp (0F57008h)  
00F41F73  call        @__CheckForDebuggerJustMyCode@4 (0F4142Eh)


int num1 = 1;

00F41F78  mov         dword ptr [num1],1


int num2 = 2;

00F41F7F  mov         dword ptr [num2],2


int myNum = Add(num1, num2);

00F41F86  mov         eax,dword ptr [num1]  
00F41F89  add         eax,dword ptr [num2]  
00F41F8C  mov         dword ptr [myNum],eax  

}


通過觀察匯編代碼,我發現經過內聯處理后的匯編代碼可以直接進行兩個參數的累加而不需要去調用Add函數。


當然你也可以在這里【參考6:Compiler Explorer】試試其他的編譯器,如GCC、ICC、Clang。關于VS控制內聯的參數,可以看這里【參考7:Microsoft Doc Inline Option】。


后來,我又看了《深入探索C++對象模型》這本書,印象很深的就是我們以為的代碼在編譯器處理后并不是我們以為的那樣,里面有各種mangling【參考8:name mangling】,添加各種附加代碼,那些看起來空空如也的的構造函數(析構函數同理)里面也可能有著幾十行或者上百行的復雜代碼。想象一下,你把這些構造代碼內聯的到處都是,你確定你的程序能得到優化么?



到這個階段,我發現我能稍微的理解高級語言與匯編語言之間的關系,函數調用的基本原理,程序與內存之間的關系等,現在知識圖譜大概變成這樣了:

在下個階段,我開始了解到一些編譯器相關的內容,對內聯的認識也進一步提升。

上篇文章總結了前兩個階段我對內聯以及相關知識的理解,大部分內容都是我之前的理解。這篇文章我會繼續深入分析,談談最近一個月學習的成果。


如果沒有看過上篇,建議先閱讀【這里】文中會有很多引用的參考鏈接,而且很多內容都是英文的(建議找時間慢慢學習),我會統一放到文末的位置,很多鏈接不適合在微信里面查看,建議用瀏覽器打開。



03:進階階段


由于前一陣總結的文章被指出inline的總結內容有諸多不妥,所以我開始換一個角度去理解inline。說實話,大佬文章中很多名詞我聽都沒聽過,因為之前除了學完編譯原理這門課之后就完全與編譯器拜拜了(雖然我無時無刻不在用IDE提供的編譯器)。


首先是關于內聯的意義,前面說過內聯的直接優點就是減少函數調用,這個是毋庸置疑的,但是他更大意義是它允許編譯器進行進一步優化

【參考1:Inline expansion ;Reducing Indirect Function Call Overhead In C++ Programs;CppCon 2014: Andrei Alexandrescu "Optimization Tips" 】。


這點是我之前沒有去想過,因為我們平時都在寫業務代碼,大部分情況下不需要考慮語言層面的問題。不過,我個人處于游戲行業,對“優化”一詞還是比較敏感的,每次編譯引擎(項目)所花費的時間、運行時的效率、調試效率、游戲幀數、打包時間等這些其實與我們的業務是息息相關的。一個龐大的項目一旦編譯起來就花費很長時間,所以會有Debug、Development、Shipping等各種版本來滿足我們不同情況下的需求。想要調試一個項目,當然是盡可能把優化都關掉才好;對于一個發行出去的游戲,當然是越小巧、高度優化、執行效率越高越好了。然而這些工作其實都是編譯器在默默的幫助我們去做的(也可以說是各位編譯領域相關的大佬幫我們做的),這時候我突然覺得我們連Debug與Release配置都搞不清真的有點對不起他們的工作了。


還是拿剛才的代碼來說,我們再看一下內聯后的匯編代碼。


int main(){  //......省略  int num1 = 1;  00F41F78  mov         dword ptr [num1],1    int num2 = 2;  00F41F7F  mov         dword ptr [num2],2    int myNum = Add(num1, num2);  00F41F86  mov         eax,dword ptr [num1]    00F41F89  add         eax,dword ptr [num2]    00F41F8C  mov         dword ptr [myNum],eax  }


對于任何一個能看懂代碼的人,我們都知道myNum就是2,所以集人類智慧于一身的編譯器也應該知道。除了把函數return a+b這段代碼內聯過來之后還應該直接算出答案,這就是說inline后的代碼與之前已經完全不同了,所以編譯器也有必要再看看這個地方有沒有什么值得優化的。事實證明如果我把這個程序改為release版本的,這段代碼就直接返回了,不客氣的說,我連 myNum=2 這個都可以直接優化掉,因為這個局部變量看起來并沒有什么意義。雖然不同的編譯器的反匯編代碼有所不一樣,但是他們都在努力的用內聯去調整編譯后的結果。

樸素一點的理解,所謂的內聯就是為了方便編譯器看到更多源碼信息,如果我們能把所有函數內聯到Main函數里面,那理論上我們可以就可以得到最佳的優化代碼,可能一段非常復雜的代碼到最后只要一個指令就足夠了。關于編譯器的優化方案,非常多而且大佬們還在不斷的優化提出更多的優化方案,常見的有死代碼刪除、循環不變代碼外提、常數折疊等等。

【參考2:Category:Compiler optimizations Reducing ;Indirect Function Call Overhead In C++ Programs】


既然談到新的優化方案,就正好說一下虛函數調用。在比較老的編譯器上,我們不會去對虛函數內聯,原因很簡單,因為虛函數的執行屬于運行時動態,我們需要動態查閱虛函數表來找到對應的虛函數。由于根本不知道運行的時候到底是哪個類會執行這個虛函數,當然也就不知道到底調用的是哪個子類下override的版本。但是,大佬們自然不會輕易放棄,能優化一點咱們就盡量優化一點。當我們的編譯器可以分析出當前的程序,如

struct A{      virtual ~A() {}      virtual int foo() { return 0; }};
inline int do_something(A& obj){ return obj.foo();}
struct B : A{ virtual ~B() {} virtual int foo() { return 1; }};
int main(){ B b; return do_something(b);}


這樣的代碼的時候,我們就可以確定B就是繼承樹上的最終的子節點,也就可以將虛函數的查表調用改為直接調用進而進行內聯優化,這種優化方式叫做Devirtualization。當然,這種代碼確實過于理想的簡單,我們常見的項目代碼一定是分為多個編譯單元的,編譯器想進行跨編譯單元的優化就還需要另一個方案,LTO(Link Time Optimization)即鏈接時優化。

【參考3: C++ Devirtualization Devirtualization in LLVM and Clang】


LTO顧名思義,就是在編譯器進行鏈接時進行相關的代碼優化,不同編譯單元在鏈接的時候將其內部表示轉儲到磁盤,然后組成單個模塊并進行優化。也因此,之前大佬糾正我說“寫在cpp里面的函數也可以內聯,每次修改會重新編譯頭文件增加編譯時間這句話也說錯誤的”?!緟⒖?:Link-time optimization for the kernel LLVM Link Time Optimization: Design and Implementation】

可是看完LTO相關資料后,我又產生了疑問,編譯器優化不是還有IPO么。所謂IPO,Interprocedural Optimization,即過程間優化,傳統的編譯器是先將編譯每個源文件成獨立的目標文件,然后再通過鏈接器將目標文件鏈接成可執行文件(或庫),其編譯優化主要集中在每個源文件內部,而IPO可以打破這個局限對整個程序進行全局的優化。那么IPO與LTO是什么關系呢?看了wiki上的資料后,我大概理解為,LTO屬于IPO的子集,IPO是一個可以在編譯過程的任何階段都能執行優化的解決方案,LTO只針對鏈接時優化,不過應該屬于IPO一個最強有力的方案了?!緟⒖?:Interprocedural optimization 現代C/C++編譯器有多智能?能做出什么厲害的優化?】另外,C/C++這種純編譯型語言都可以做到鏈接時內聯優化,而對于C#、Java這種半編譯半解釋型的語言,其優化的時機豈不是可以更為靈活隨意了?


這時候我才發現,這么多關于內聯的調整的優化好像都是編譯器在搞,無論什么語言、什么平臺,本質上都逃不過編譯器的審核與優化。


那么,我們顯示聲明inline還有什么意義呢?好像我們寫不寫inline,沒什么意義啊。只要是發行出去的版本,編譯器自己決定,分分鐘給你各種優化,你的各種inline建議好像都沒什么意義了。不過,帶著疑問我又去查了查資料,發現好像還沒問想的那么簡單,最起碼inline還有兩點意義:

1.   編譯器并不是萬能的,有時候人工的內聯建議確實能解決一些編譯器優化的盲點【參考6:libcxx修改1 、libcxx修改2 】

2.   inline并不只是只有把函數內聯到調用的地方這個意義,他還關系到ODR (定義與單一定義規則)。所謂ODR,就是任何變量、函數、類類型、枚舉類型、概念 (C++20 起)或模板,在每個翻譯單元中都只允許有一個定義(它們有些可以有多個聲明,但定義只允許有一個)。不過具體一點的說又有很多種情況,對于非inline且odr-used的變量、函數,要求全局只能有一個定義;Inline變量、函數在每個編譯單元都有有一個定義;需要使用類的時候,在每個翻譯單元都需要一個定義。

例如:你如果把一個非成員函數放到.h文件里面并被多個編譯單元包含,那么在鏈接的時候就會報錯。因為非inline的全局函數在全局只能有一個定義,如果每個編譯單元都有一個成員函數,編譯器不知道鏈接哪一個。如果給這個函數加上inline的話,就可以解決這個問題。而如果你在多個cpp里面定義了函數簽名完全相同的但是內容不同inline函數,也不會發生編譯失敗,不過具體鏈接到哪個版本的inline函數可能是未定義行為?!緟⒖?:One Definition Rule  既然編譯器可以判斷一個函數是否適合 inline,那還有必要自己加 inline 關鍵字嗎?最近看到陳碩的一本書提了一個問題,“編譯器如何處理inline函數中的static變量?”】


關于優化,這里還涉及到一個概念, zero-overhead abstraction,即“零代價的抽象”(Rust里面叫zero-cost abstractions),簡單來說就是抽象的同時不需要付出額外的代價,比如說vector<int> 數組在優化理想的編譯器的發行版本下與int類型的數組開銷應該是幾乎相同的。因此,我們可以認為C++中的zero-overhead abstraction與編譯器的優化是密不可分的,更進一步的說,C++語言本身的優良與編譯器也是密不可分的。每當C++新的標準出來后,各大編譯器團隊都要及時的去支持這些新的特性,并兼顧語言的優化問題?!緟⒖?:Rust所宣稱的zero-cost abstractions是怎么回事?Zero-cost abstractions abstraction and hand-crafted code】


最后,再簡單提一下LLVM。前面的很多鏈接里面都提到了LLVM(很久以前,LLVM曾經是Low Level Virtual Machine的縮寫,現在已經不是了),它是一個編譯器基礎設施框架,包含了我們編寫編譯器需要的一系列庫(如程序分析、代碼優化、機器代碼生成等),并且提供了調用這些庫的相關工具。Clang, llbc++, lld等項目就是基于LLVM開發的,Objective-C編譯器也是基于LLVM開發的。LLVM的編譯過程與傳統的編譯器有所差異(參考下圖),中間會生成一套通用的與語言無關的中間語言LLVM IR,我們可以去閱讀這個中間語言了解更多信息【參考9:測試工具】,所以編譯優化與使用方式也是非常靈活(我目前在VS上還沒有找到可以閱讀的單個編譯單元編譯后的文件)

我沒有深入了解,更多內容可以【參考10:LLVM 誰說不能與龍一起跳舞:Clang / LLVM (1) 周末花了點時間看 LLVM IR, 閑扯幾句 編譯器LLVM淺淺玩 】


這段時間查了各種資料,算是勉強到了一個新的階段。這個階段的我開始關注了core language之外的東西——編譯器,雖然研究的并不是很透徹,但是在概念上我對程序的運行編譯、程序與操作系統之間的關系有了進一步的理解,有時候會覺得豁然開朗,看起來毫無關系的知識突然就聯系在了一起。

知識圖譜又稍微修改了一下:



也許這篇文章看完,你會覺得這個內聯好像懂了以后對編程也沒多少幫助,但正如我知乎上的評論所說的,


其實這個就像我們學的數理化那些,看起來生活中很少或者基本不會直接用到,但是里面的思想會影響到我們對事物做出的邏輯判斷,甚至萬一遇到問題了還能解決,就拿匯編來說,大部分做業務的程序員基本不會用到,但是一旦遇到問題甚至要在沒有單步調試環境的下面debug,反編譯后能看下匯編代碼,調試起來還是會比不會匯編的人方便快捷很多。很多東西怕就怕萬一用到了~

End


參考鏈接:

【1】.Inline expansion ;

https://en.wikipedia.org/wiki/Inline_expansion

CppCon 2014: Andrei Alexandrescu "Optimization Tips" 

https://www.youtube.com/watch?v=Qq_WaiwzOtI


【2】.Category:Compiler optimizations Reducing ;

https://en.wikipedia.org/wiki/Category:Compiler_optimizations

Reducing Indirect Function Call Overhead In C++ Programs;

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.27.5761&rep=rep1&type=pdf 


【3】.C++ Devirtualization 

http://lazarenko.me/devirtualization/ 

Devirtualization in LLVM and Clang

http://link.zhihu.com/?http://blog.llvm.org/2017/03/devirtualization-in-llvm-and-clang.html 


【4】.Link-time optimization for the kernel LLVM 

http://llvm.org/docs/LinkTimeOptimization.html

Link Time Optimization: Design and Implementation

https://lwn.net/Articles/512548/ 


【5】.Interprocedural optimization 

https://en.wikipedia.org/wiki/Interprocedural_optimization 

現代C/C++編譯器有多智能?能做出什么厲害的優化?

https://www.zhihu.com/question/43598164/answer/122186527 


【6】.libcxx修改

https://reviews.llvm.org/D22782 

https://reviews.llvm.org/D22834 


【7】.One Definition Rule 

https://en.wikipedia.org/wiki/One_Definition_Rule

既然編譯器可以判斷一個函數是否適合 inline,那還有必要自己加 inline 關鍵字嗎?

https://www.zhihu.com/question/53082910 


【8】.Rust所宣稱的zero-cost abstractions是怎么回事?

 https://www.zhihu.com/question/31645634

Zero-cost abstractions

https://ruudvanasseldonk.com/2016/11/30/zero-cost-abstractions

Interview with Bjarne Stroustrup - abstraction and hand-crafted code

https://stackoverflow.com/questions/20134585/interview-with-bjarne-stroustrup-abstraction-and-hand-crafted-code 


【9】.測試工具 http://ellcc.org/demo/index.cgi 


【10】.LLVM

http://www.aosabook.org/en/llvm.html 

誰說不能與龍一起跳舞:Clang / LLVM (1) 

https://zhuanlan.zhihu.com/p/21889573 

周末花了點時間看 LLVM IR, 閑扯幾句 

https://segmentfault.com/a/1190000002669213 

編譯器LLVM淺淺玩 

https://medium.com/@zetavg/%E7%B7%A8%E8%AD%AF%E5%99%A8-llvm-%E6%B7%BA%E6%B7%BA%E7%8E%A9-42a58c7a7309 




亚洲欧美第一页_禁久久精品乱码_粉嫩av一区二区三区免费野_久草精品视频
国产精品青草久久久久福利99| 浪潮色综合久久天堂| 久久久夜精品| 亚洲国产91精品在线观看| 欧美日韩国产bt| 久久精品99久久香蕉国产色戒| 亚洲高清视频一区| 国产精品久久77777| 久久先锋资源| 亚洲中午字幕| 亚洲国产一区二区a毛片| 国产精品大片| 久久性天堂网| 亚洲欧美国产精品va在线观看 | 久久精品人人做人人综合| 欧美激情第1页| 亚洲综合成人婷婷小说| 亚洲美女精品久久| 亚洲电影激情视频网站| 黄色一区二区三区| 日韩午夜av在线| 国模叶桐国产精品一区| 国产精品亚洲人在线观看| 欧美另类在线观看| 久久在线免费观看| 久久av一区二区三区亚洲| 亚洲欧美日韩精品| 亚洲在线视频免费观看| 亚洲小说欧美另类社区| 一区二区精品国产| 一本不卡影院| 一区二区三区高清不卡| 日韩午夜精品视频| 亚洲精品孕妇| 一区二区不卡在线视频 午夜欧美不卡'| 亚洲国产成人精品久久久国产成人一区| 国产欧美综合一区二区三区| 国产精品亚洲一区| 国产精品一区二区久久| 国产欧美丝祙| 有码中文亚洲精品| 91久久精品一区二区别| 亚洲精品一区二区三区在线观看| 亚洲欧洲精品一区| 一区二区三区福利| 亚洲午夜一区二区三区| 91久久精品国产91久久性色| 亚洲丰满在线| 亚洲精品久久久久久久久久久久 | 欧美日本在线观看| 欧美女激情福利| 国产精品99久久久久久有的能看| 亚洲毛片在线| 一区二区免费在线视频| 在线亚洲美日韩| 亚洲综合久久久久| 久久久久免费观看| 久热这里只精品99re8久| 欧美激情网站在线观看| 欧美色道久久88综合亚洲精品| 欧美日韩一区三区| 国产日韩三区| 亚洲成人在线免费| 激情久久久久久久| 亚洲理伦电影| 亚洲国产cao| 久久久噜噜噜久久狠狠50岁| 蜜桃av久久久亚洲精品| 欧美日本精品一区二区三区| 国产精品午夜视频| 久久久精品日韩欧美| 欧美99久久| 国产精品免费久久久久久| 国产一区激情| 亚洲少妇在线| 亚洲欧美国产一区二区三区| 麻豆精品视频在线观看视频| 欧美日韩1区2区| 国产精品专区一| 国产精品中文字幕在线观看| 亚洲国产精品久久久久秋霞不卡| 亚洲欧美日韩一区二区在线 | 国产真实久久| 一本一本大道香蕉久在线精品| 欧美一级片在线播放| 欧美精品二区三区四区免费看视频| 国产精品伦理| 亚洲日本无吗高清不卡| 性欧美超级视频| 欧美日本在线一区| 一区二区三区在线看| 中文在线一区| 免费永久网站黄欧美| 国产精品人人做人人爽| 国产精品色婷婷| 这里只有精品在线播放| 国产色综合网| 国产精品久久久久久久久久ktv| 国产欧美日韩伦理| 亚洲国产一区二区三区a毛片 | 99国产一区| 久久久人人人| 亚洲综合二区| 欧美日韩国产成人在线观看| 一区二区视频欧美| 久久精品夜色噜噜亚洲aⅴ| 欧美视频一区二区三区四区| 亚洲精品乱码视频| 牛人盗摄一区二区三区视频| 久久久久一本一区二区青青蜜月| 国产精品黄页免费高清在线观看| 亚洲精品国产精品乱码不99按摩 | 国产精品一区二区在线观看不卡| 在线日韩中文字幕| 久久精品首页| 激情综合色丁香一区二区| 亚洲午夜一级| 欧美精品一区二区三区在线看午夜| 在线观看91精品国产入口| 久久精品一区二区三区不卡牛牛 | 久久久久久久高潮| 国产一区二区三区观看| 欧美一区二区三区婷婷月色| 欧美激情国产日韩| 91久久精品日日躁夜夜躁欧美 | 亚洲视频中文字幕| 欧美日韩在线一二三| 日韩视频在线你懂得| 欧美日韩成人免费| 影音先锋中文字幕一区二区| 噜噜噜久久亚洲精品国产品小说| 亚洲电影网站| 欧美精品在线免费播放| 在线一区二区三区四区五区| 国产精品美女在线| 午夜久久福利| 欧美日韩三级视频| 99re在线精品| 国产精品卡一卡二| 亚洲影院色无极综合| 欧美午夜大胆人体| 亚洲高清免费视频| 欧美mv日韩mv国产网站| 国产精品一区二区女厕厕| 亚洲国产精品久久久久| 欧美日韩精品一区二区在线播放 | 久久久久久久91| 国产精品视频一| 99re这里只有精品6| 国产精品中文字幕欧美| 久久只精品国产| 一本久道久久久| 国产精品久久久久久影院8一贰佰| 欧美在线高清视频| 亚洲国产精彩中文乱码av在线播放| 麻豆久久精品| 极品av少妇一区二区| 欧美麻豆久久久久久中文| 亚洲一区二区久久| 国产嫩草一区二区三区在线观看 | 狠狠色狠狠色综合人人| 欧美乱大交xxxxx| 在线播放中文一区| 欧美视频免费| 亚洲欧美中文日韩在线| 伊人狠狠色j香婷婷综合| 久久精品视频99| 亚洲视频999| 永久免费精品影视网站| 国产精品区免费视频| 能在线观看的日韩av| 午夜精品亚洲| 一本久久综合亚洲鲁鲁五月天| 国内一区二区三区在线视频| 欧美日韩国产页| 麻豆精品一区二区av白丝在线| 99香蕉国产精品偷在线观看| 最新国产乱人伦偷精品免费网站 | 性久久久久久| 99国产精品国产精品久久| 一区二区三区在线免费视频| 国产精品剧情在线亚洲| 欧美日韩播放| 欧美成人一区二区在线| 欧美韩日一区| 另类成人小视频在线| 亚洲精品免费在线播放| 激情久久久久久久| 国产女主播在线一区二区| 国产精品久久久免费| 欧美日韩亚洲视频| 欧美精品亚洲二区| 欧美大片免费久久精品三p | 麻豆精品网站| 久久久久久久999| 久久精品国产99| 欧美在线啊v一区| 欧美一区二区三区免费观看| 亚洲一区二区四区| 亚洲一区二区精品视频|