亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

C語言中可變參數va_list/va_start/value_arg/va_end的理解

2019-11-06 06:04:21
字體:
來源:轉載
供稿:網友

va_list/va_start/va_arg/va_end這幾個宏,都是用于函數的可變參數的。

我們來看看在vs2008中,它們是怎么定義的:

   1:  ///stdarg.h
   2:  #define va_start _crt_va_start
   3:  #define va_arg _crt_va_arg
   4:  #define va_end _crt_va_end
   5:   
   6:  ///vadefs.h
   7:  #define _ADDRESSOF(v)   ( &reinterPRet_cast<const char &>(v) )
   8:  typedef char *  va_list;
   9:  #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
  10:  #define _crt_va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
  11:  #define _crt_va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
  12:  #define _crt_va_end(ap)      ( ap = (va_list)0 )
 
再看看各個宏的功能是什么?va_list用于聲明一個變量,我們知道函數的可變參數列表其實就是一個字符串,所以va_list才被聲明為字符型指針,這個類型用于聲明一個指向參數列表的字符型指針變量,例如:va_list ap;//ap:arguement pointerva_start(ap,v),它的第一個參數是指向可變參數字符串的變量,第二個參數是可變參數函數的第一個參數,通常用于指定可變參數列表中參數的個數。va_arg(ap,t),它的第一個參數指向可變參數字符串的變量,第二個參數是可變參數的類型。va_end(ap) 用于將存放可變參數字符串的變量清空(賦值為NULL).

  先來了解C語言中可變參數函數實現原理

C函數調用的棧結構

 可變參數函數的實現與函數調用的棧結構密切相關,正常情況下C的函數參數入棧規則為__stdcall, 它是從右到左的,即函數中的最右邊的參數最先入棧。例如,對于函數:

  void fun(int a, int b, int c)  {        int d;        ...  }

其棧結構為

    0x1ffc-->d

    0x2000-->a

    0x2004-->b

    0x2008-->c

對于在32位系統的多數編譯器,每個棧單元的大小都是sizeof(int), 而函數的每個參數都至少要占一個棧單元大小,如函數 void fun1(char a, int b, double c, short d) 對一個32的系統其棧的結構就是

    0x1ffc-->a  (4字節)(為了字對齊)

    0x2000-->b  (4字節)

    0x2004-->c  (8字節)

    0x200c-->d  (4字節)

因此,函數的所有參數是存儲在線性連續的??臻g中的,基于這種存儲結構,這樣就可以從可變參數函數中必須有的第一個普通參數來尋址后續的所有可變參數的類型及其值。

先看看固定參數列表函數:

void fixed_args_func(int a, double b, char *c){        printf("a = 0x%p/n", &a);        printf("b = 0x%p/n", &b);        printf("c = 0x%p/n", &c);}

對于固定參數列表的函數,每個參數的名稱、類型都是直接可見的,他們的地址也都是可以直接得到的,比如:通過&a我們可以得到a的地址,并通過函數原型聲明了解到a是int類型的。

   但是對于變長參數的函數,我們就沒有這么順利了。還好,按照C標準的說明,支持變長參數的函數在原型聲明中,必須有至少一個最左固定參數(這一點與傳統C有區別,傳統C允許不帶任何固定參數的純變長參數函數),這樣我們可以得到其中固定參數的地址,但是依然無法從聲明中得到其他變長參數的地址,比如:

void var_args_func(const char * fmt, ...) {    ... ... }

這里我們只能得到fmt這固定參數的地址,僅從函數原型我們是無法確定"..."中有幾個參數、參數都是什么類型的?;叵胍幌潞瘮祩鲄⒌倪^程,無論"..."中有多少個參數、每個參數是什么類型的,它們都和固定參數的傳參過程是一樣的,簡單來講都是棧操作,而棧這個東西對我們是開放的。這樣一來,一旦我們知道某函數幀的棧上的一個固定參數的位置,我們完全有可能推導出其他變長參數的位置。

我們先用上面的那個fixed_args_func函數確定一下入棧順序。

復制代碼
int main() {    fixed_args_func(17, 5.40, "hello world");    return 0;}a = 0x0022FF50b = 0x0022FF54c = 0x0022FF5C復制代碼

從這個結果來看,顯然參數是從右到左,逐一壓入棧中的(棧的延伸方向是從高地址到低地址,棧底的占領著最高內存地址,先入棧的參數,其地理位置也就最高了)。

我們基本可以得出這樣一個結論:

 c.addr = b.addr + x_sizeof(b);  /*注意:  x_sizeof !=sizeof */ b.addr = a.addr + x_sizeof(a);

有了以上的"等式",我們似乎可以推導出 void var_args_func(const char * fmt, ... ) 函數中,可變參數的位置了。起碼第一個可變參數的位置應該是:first_vararg.addr = fmt.addr + x_sizeof(fmt);  根據這一結論我們試著實現一個支持可變參數的函數:

復制代碼
#include <stdarg.h>#include <stdio.h>void var_args_func(const char * fmt, ...) {    char    *ap;    ap = ((char*)&fmt) + sizeof(fmt);    printf("%d/n", *(int*)ap);              ap =  ap + sizeof(int);    printf("%d/n", *(int*)ap);    ap =  ap + sizeof(int);    printf("%s/n", *((char**)ap));}int main(){    var_args_func("%d %d %s/n", 4, 5, "hello world");   return 0;}復制代碼

期待輸出結果:45hello world


  先來解釋一下這個程序。我們用ap獲取第一個變參的地址,我們知道第一個變參是4,一個int 型,所以我們用(int*)ap以告訴編譯器,以ap為首地址的那塊內存我們要將之視為一個整型來使用,*(int*)ap獲得該參數的值;接下來的變參是5,又一個int型,其地址是ap + sizeof(第一個變參),也就是ap + sizeof(int),同樣我們使用*(int*)ap獲得該參數的值;最后的一個參數是一個字符串,也就是char*,與前兩個int型參數不同的是,經過ap + sizeof(int)后,ap指向棧上一個char*類型的內存塊(我們暫且稱之tmp_ptr, char *tmp_ptr)的首地址,即ap -> &tmp_ptr,而我們要輸出的不是printf("%s/n", ap),而是printf("%s/n", tmp_ptr); printf("%s/n", ap)是意圖將ap所指的內存塊作為字符串輸出了,但是ap -> &tmp_ptr,tmp_ptr所占據的4個字節顯然不是字符串,而是一個地址。如何讓&tmp_ptr是char **類型的,我們將ap進行強制轉換(char**)ap <=> &tmp_ptr,這樣我們訪問tmp_ptr只需要在(char**)ap前面加上一個*即可,即printf("%s/n",  *(char**)ap);


   一切似乎很完美,編譯也很順利通過,但運行上面的代碼后,不但得不到預期的結果,反而整個編譯器會強行關閉(大家可以嘗試著運行一下),原來是ap指針在后來并沒有按照預期的要求指向第二個變參數,即并沒有指向5所在的首地址,而是指向了未知內存區域,所以編譯器會強行關閉。其實錯誤開始于:ap =  ap + sizeof(int);由于內存對齊,編譯器在棧上壓入參數時,不是一個緊挨著另一個的,編譯器會根據變參的類型將其放到滿足類型對齊的地址上的,這樣棧上參數之間實際上可能會是有空隙的。(C語言內存對齊詳解(1) C語言內存對齊詳解(2) C語言內存對齊詳解(3))所以此時的ap計算應該改為:ap =  (char *)ap +sizeof(int) + __va_rounded_size(int);

改正后的代碼如下:

復制代碼
#include<stdio.h>#define __va_rounded_size(TYPE)  /  (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))void var_args_func(const char * fmt, ...) {    char *ap;    ap = ((char*)&fmt) + sizeof(fmt);    printf("%d/n", *(int*)ap);              ap = (char *)ap + sizeof(int) + __va_rounded_size(int);    printf("%d/n", *(int*)ap);    ap = ap + sizeof(int) + __va_rounded_size(int);    printf("%s/n", *((char**)ap));}int main(){    var_args_func("%d %d %s/n", 4, 5, "hello world");     return 0;}復制代碼

var_args_func只是為了演示,并未根據fmt消息中的格式字符串來判斷變參的個數和類型,而是直接在實現中寫死了。

為了滿足代碼的可移植性,C標準庫在stdarg.h中提供了諸多便利以供實現變長長度參數時使用。這里也列出一個簡單的例子,看看利用標準庫是如何支持變長參數的:

復制代碼
 1 #include <stdarg.h>#include <stdio.h> 2  3 void std_vararg_func(const char *fmt, ...) { 4         va_list ap; 5         va_start(ap, fmt); 6  7         printf("%d/n", va_arg(ap, int)); 8         printf("%f/n", va_arg(ap, double)); 9         printf("%s/n", va_arg(ap, char*));10 11         va_end(ap);12 }13 14 int main() {15         std_vararg_func("%d %f %s/n", 4, 5.4, "hello world");        return 0;}復制代碼

對比一下 std_vararg_func和var_args_func的實現,va_list似乎就是char*, va_start似乎就是 ((char*)&fmt) + sizeof(fmt),va_arg似乎就是得到下一個參數的首地址。沒錯,多數平臺下stdarg.h中va_list, va_start和var_arg的實現就是類似這樣的。一般stdarg.h會包含很多宏,看起來比較復雜。

下面我們來探討如何寫一個簡單的可變參數的C 函數.

使用可變參數應該有以下步驟: 1)首先在函數里定義一個va_list型的變量,這里是arg_ptr,這個變量是指向參數的指針. 2)然后用va_start宏初始化變量arg_ptr,這個宏的第二個參數是第一個可變參數的前一個參數,是一個固定的參數. 3)然后用va_arg返回可變的參數,并賦值給整數j. va_arg的第二個參數是你要返回的參數的類型,這里是int型. 4)最后用va_end宏結束可變參數的獲取.然后你就可以在函數里使用第二個參數了.如果函數有多個可變參數的,依次調用va_arg獲取各個參數.

在《C程序設計語言》中,Ritchie提供了一個簡易版printf函數:

復制代碼
 1 #include<stdarg.h> 2  3 void minprintf(char *fmt, ...) 4 { 5     va_list ap; 6     char *p, *sval; 7     int ival; 8     double dval; 9 10     va_start(ap, fmt);11     for (p = fmt; *p; p++) {12         if(*p != '%') {13             putchar(*p);14             continue;15         }16         switch(*++p) {17         case 'd':18             ival = va_arg(ap, int);19             printf("%d", ival);20             break;21         case 'f':22             dval = va_arg(ap, double);23             printf("%f", dval);24             break;25         case 's':26             for (sval = va_arg(ap, char *); *sval; sval++)27                 putchar(*sval);28             break;29         default:30             putchar(*p);31             break;32         }33     }34     va_end(ap);35 }

我們看一段具有可變參數列表的函數的代碼:

  #include <stdio.h>#include <stdarg.h>void simple_va_fun(int i, ...){        va_list ap;        int j = 0;        int k = 0;        va_start(ap, i);        j = va_arg(ap, int);        k = va_arg(ap, int);        va_end(ap);        printf("%d %d %d/n",i,j,k);        return;}int main(void){        simple_va_fun(100);        simple_va_fun(100,200);        simple_va_fun(100,200,300);        return 0;}

 

輸出結果:100 730930504 730930520100 200 -965392000100 200 300

va_start的功能是要把,ap指針指向可變參數的第一個參數位置處,

    #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )

    #define _ADDRESSOF(v) ( &reinterpret_cast<constchar &>(v) ):reinterpret_cast是C++新標準下的強制類型轉換,這里將v強制轉換為const char*型,然后取其地址。

   先取第一個參數的地址,在sum函數中就是取number的地址并且將其轉化為char *的(因為char *的指針進行加減運算后,偏移的字節數才與加的數字相同, 如果為int *p,那么p+1實際上將p移動了4個字節),然后加上4(__INITSIZEOF(number)=(4+3)&~3),這樣就將ap指向了可變參數字符串的第一個參數。

 

  #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

 

以int所占的字節為標準進行對其操作。如果int占四字節,則以四字節對齊為標準讀取數據。

至于為什么會這樣計算是考慮到內存對齊

兩年之前我寫過一篇可變參數學習筆記,里面曾經簡單的解釋過一句:代碼((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))的作用是在考慮字節對齊的因素下計算第一個可變參數的起始地址。當時限于時間和水平,未能做更詳細的解釋。今天(2007-11-26)在csdn論壇上看到了一個帖子http://topic.csdn.net/u/20071123/16/c8d17d3f-9f49-49af-a6d8-1d7a7d84dc1c.html?seed=303711257問題:CRT源碼分析中一個關于可變函數參數的問題提問者:Sun_Moon_Stars里面又問到了這個宏,于是決定抽出半天時間,把這個問題詳細的說清楚。也算是把我的那篇文章做一個完美的結尾。

二、引子先看一個日常生活中的問題,問題1:假設有要把一批貨物放到集裝箱里,貨物有12件,一個箱子最多能裝6件貨物,求箱子的數目。解答:顯然我們需要12/6=2個箱子,并且每個箱子都是滿的。這個連小學生都會算:-)

問題2:       把問題1的條件改一下,假設一個箱子最多能裝5件貨物,那么現在的箱子數是多少?解答:       12/5=2.4個,但是根據實際情況,箱子的個數必須為整數,(有不知道這個常識的就不要再往下看了,回小學重讀吧,呵呵)自然我們就要取3,下面把問題一般化

三、一般數學模型問題3:設一個箱子最多可以裝M件貨物,且現有N件貨物,則至少需要多少個箱子,給出一般的計算公式。這里要注意兩點1、箱子的總數必須為整數2、N不一定大于M,很顯然,即使N<m,也得需要一只箱子 <="" p="" style="Word-wrap: break-word;">

四、通項公式1、預備知識在討論之問題3的解答之前,我們先明確一下/運算符的含義。定義/運算為取整運算,即對任意兩個整數N,M,必然有且只有唯一的整數X,滿足X*M   <=   N   <   (X+1)*M,那么記N/M=X。這個也正是c里/運算的確切含義。x的存在性和唯一性的嚴格證明可以見數論教材。以后如無額外說明,/運算的含義均和本處一致。

/運算有一個基本的性質若N=MX+Y,則N/M=X+Y/M,證明略

注意:N不是可以隨便拆的,設N=A+B,那么一般情況下N/M   不一定等于   A/M+B/M,如果A和B至少有一個是M的倍數,才能保證式子一定成立。

2、分步討論根據上面的/運算符的定義,我們可以得到問題三的解答,分情況討論一下已知N/M=X,那么當(1)、當N正好是M的倍數時即N=M*X時,那么箱子數就是X=N/M(2)、如果N不是M的倍數,即N=M*X+Y(1 <=Y <m)時那么顯然還要多一個箱子來裝余下的Y件貨物,則箱子總數為X+1   =   N/M+1

3、一般公式上面的解答雖然完整,但是用起來并不方便,因為每次都要去判斷N和M的倍數關系,我們自然就要想一個統一的公式,于是,下面的公式出現了箱子數目為     (N+M-1)/M

這個式子用具體數字去驗證是很簡單的,留給讀者去做。我這里給一個完整的數學推導:現在已經假定   /運算的結果為取整(或者說取模),即N/M=X,則XM   <=N   <(X+1)M那么,(1)、當N=MX時,(N+M-1)/M=   MX/M+(M-1)/M=X(2)、當N=MX+Y(1 <=Y <m)時,由1 <=Y   <   M,同時加上M-1,得到M   <=   Y-1+M   <=   2M-1   <2M根據   /運算的定義   (Y-1+M)   /M   =   1

所以 (N+M-1)/M   =   (MX+Y+M-1)/M=   MX/M+(Y+M-1)/M=   X+1顯然   公式   (N+M-1)/M與2中的分步討論結果一致??赡苡械淖x者還會問,這個公式是怎么想出來的,怎么就想到了加上那個M-1?這個問題可以先去看看數論中的余數理論。

五、對齊代碼的分析有了上面的數學基礎,我們再來看看開頭所說的對齊代碼的含義((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))意義就很明顯了這里。機器字長度sizeof(int)相當于箱子的容量M,變量的真實字節大小相于貨物總數N,整個代碼就是求n所占的機器字數目。

順便仔細的解釋一下~(sizeof(int)-1))

這里用到了一個位運算的技巧,即若M是2的冪,M=power(2,Y);

則N/M   =  N>>Y  ,

另根據數論中的余數定理,

有N=M*X+Z(1 <   =Z <  M)而注意到這里的N,M,Z都是二進制表示,所以把N的最右邊的Y位數字就是余數Z.剩下的左邊數字就是模X.

而內存對齊要計算的是占用的總字節數(相當于箱子的最大容量),所以

總字節數 = ( N/M)*M =( N>>Y)<<y <="" p="" style="word-wrap: break-word;">

注意,這里的右移和左移運算并未相互抵消,最后的結果實際上是把N中的余數Z去掉(被清0),

而左邊模X得以保持不變。

而當M = power(2,Y) 時

(N >>Y) << Y = (N   &(~(M-1))也是一個恒等式(這個讀者也可以用數字驗證),

所以,就得到我們前面看到的宏

((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

注意:(1)這里最關鍵的一點就是M必須是2的冪(有人常常理解成2的倍數也可以,那是不對的),否則上面的結論是不成立的(2)   ~(M-1)更專業的叫法就是掩碼(mask)。因為數字和這個掩碼進行與運算后,數字的最右邊Y位的數字被置0("掩抹"掉了).即掩碼最右邊的0有多少位,數字最右邊就有多少位被清0。

小結:1、字節對齊的數學本質就是數論中的取模運算。在計算機上的含義就是求出一個對象占用的機器字數目。2、在數學上看內存計算的過程就是先右移再左移相同的位數,以得到箱子的最大容量。

3、在c中/運算可以用位運算和掩碼來實現以加快速度(省掉了求位數的過程),前提是機器字長度必須為2的冪。

——————————————————

#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1)

[此問題的推薦答案]~是位取反的意思。_INTSIZEOF(n)整個做的事情就是將n的長度化為int長度的整數倍。比如n為5,二進制就是101b,int長度為4,二進制為100b,那么n化為int長度的整數倍就應該為8。~(sizeof(int) – 1) )就應該為~(4-1)=~(00000011b)=11111100b,這樣任何數& ~(sizeof(int) – 1) )后最后兩位肯定為0,就肯定是4的整數倍了。(sizeof(n) + sizeof(int) – 1)就是將大于4m但小于等于4(m+1)的數提高到大于等于4(m+1)但小于4(m+2),這樣再& ~(sizeof(int) – 1) )后就正好將原長度補齊到4的倍數了。

    

va_arg是要從ap中取下一個參數。

 

       #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

 

對于這個宏,哥糾結了很久,最后終于搞清楚了,究其原因就是自己C語言功底不扎實,具體表現在沒有搞清楚賦值表達式的值是怎么運作的。
我們看這個宏,首先是ap = ap + __INTSIZEOF(t)。注意到,此時ap已經被改變了,它已經指向了下一個參數,我們令x=ap + __INTSIZEOF(t);
那么括號內就變成了(x – __INTSIZEOF(t)),但是這里沒有賦值運算符,所以ap的值沒有發生變化,此時ap仍然指向的是當前參數的下一個參數的位置,
也就是說ap指向的位置比當前正在處理的位置超前了一個位置。
其實寫成下面的形式就簡單明了了:

    #define   va_arg(ap,t)   (*(t   *)((ap   +=   _INTSIZEOF(t)),   ap   -   _INTSIZEOF(t))   )

 
分析:為什么要將ap指向當前處理參數的下一個參數了?
經過上面的分析,我們知道va_start(ap,v)已經將ap指向了可變參數列表的第一個參數了,以后我們每一步操作都需要將ap移動到下一個
參數的位置,由于我們每次使用可變參數的順序是:va_start(ap,v)—>va_arg(ap,t);這樣我們在第一次去參數的時候,其實ap已經指向了
第二個參數開始的位置,所以我們用表達式的方式獲得一個指向第一個參數的臨時指針,這樣我們就可以采用這種一致的方式來處理可變參數列表。
(感覺沒表達的十分清楚,希望各位朋友糾正~~~~~~)。
下圖是我的例子程序中去參數的情況(時間倉促,畫得很丑,請原諒):
 
image 
 va_end(ap)  將聲明的ap指針置為空,因為指針使用后最后設置為空。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲国产毛片完整版| 亚洲影视中文字幕| 国产精品女主播视频| 成人网页在线免费观看| 亚洲女人天堂成人av在线| 久久精品精品电影网| 亚洲一区二区三区香蕉| 中文字幕少妇一区二区三区| 日韩av电影免费观看高清| 91国产一区在线| 日韩理论片久久| 欧美一乱一性一交一视频| 最新中文字幕亚洲| 国产精品免费看久久久香蕉| 蜜臀久久99精品久久久无需会员| 久久躁狠狠躁夜夜爽| 欧美极品欧美精品欧美视频| 久久这里有精品| 欧美日韩精品在线播放| 亚洲成色777777女色窝| 成人黄色av播放免费| 97视频在线看| 国产成人精品免高潮费视频| 国产精品视频xxx| 日韩免费视频在线观看| 精品国产一区二区三区久久| 欧美极品欧美精品欧美视频| 欧美日韩亚洲视频一区| 欧美成人合集magnet| 亚洲最大在线视频| 91在线无精精品一区二区| 夜夜躁日日躁狠狠久久88av| 国产欧美va欧美va香蕉在| 中文字幕成人在线| 亚洲国产福利在线| 韩剧1988免费观看全集| 91在线视频精品| 日韩一区二区在线视频| 国产色综合天天综合网| 国产99久久精品一区二区 夜夜躁日日躁| 国产在线观看不卡| 国产精品视频精品| 亚洲第一男人av| 精品视频www| 精品久久久一区| 欧洲日本亚洲国产区| 7777kkkk成人观看| 国产精自产拍久久久久久蜜| 国产精品高清网站| 国产精品电影网| 在线精品视频视频中文字幕| 欧美午夜精品久久久久久久| 97国产真实伦对白精彩视频8| 国产91色在线| 精品国产鲁一鲁一区二区张丽| 欧美精品日韩www.p站| 久久久久久久久亚洲| 欧美精品精品精品精品免费| 国产啪精品视频| 精品国产一区二区三区久久| 久久人人爽人人爽人人片av高清| 久久人人爽人人爽人人片亚洲| 国产精品久久久久不卡| 97色在线视频观看| 欧美激情成人在线视频| 日韩av男人的天堂| 亚洲精品免费av| 亚洲国产精品va在线观看黑人| 欧美国产亚洲精品久久久8v| 日韩精品欧美激情| 成人xxxx视频| 久久久亚洲欧洲日产国码aⅴ| 国产在线拍偷自揄拍精品| 国产一区二区三区四区福利| 国产999在线| 成人免费观看49www在线观看| 亚洲精品美女在线观看| 高清日韩电视剧大全免费播放在线观看| 国产精品高清网站| 久久久999国产| 国产日韩综合一区二区性色av| 日韩在线视频免费观看高清中文| 久久久久久久999精品视频| 国产精品十八以下禁看| 日韩av免费网站| 国产69久久精品成人| 午夜精品一区二区三区在线播放| 亚洲精品视频在线播放| 亚洲xxxxx| 欧美日韩在线一区| 国产精品视频公开费视频| 岛国视频午夜一区免费在线观看| 中文字幕国产亚洲2019| 国产成人福利夜色影视| 中文字幕日韩欧美精品在线观看| 欧美视频裸体精品| xvideos亚洲| 欧美日韩国产精品一区| 最近免费中文字幕视频2019| 热久久视久久精品18亚洲精品| 欧美亚洲国产另类| 国产美女直播视频一区| 另类视频在线观看| 亚洲欧美精品在线| 亚洲香蕉在线观看| 懂色av一区二区三区| 国产91九色视频| 自拍偷拍亚洲精品| 一级做a爰片久久毛片美女图片| 国产日本欧美一区二区三区| 精品露脸国产偷人在视频| 午夜精品久久久久久久99黑人| 国产在线精品播放| 欧美又大又粗又长| 欧美激情久久久| 欧美色视频日本高清在线观看| 精品国产一区二区三区久久久狼| 精品露脸国产偷人在视频| 国产一区二区三区日韩欧美| 少妇av一区二区三区| 亚洲一区二区三区视频播放| 欧美色另类天堂2015| 亚洲在线第一页| 国产视频精品va久久久久久| 青青久久av北条麻妃黑人| 亚洲丁香久久久| 久久久久久综合网天天| 97国产精品视频人人做人人爱| 久久99国产综合精品女同| 日韩美女写真福利在线观看| 亚洲欧美中文日韩v在线观看| 欧美性感美女h网站在线观看免费| 亚洲老头老太hd| 久久久精品美女| 欧美成人精品一区二区三区| 日韩精品www| 国产精品久久久久久久久久久新郎| 91超碰中文字幕久久精品| 国产激情视频一区| 日韩久久精品电影| 欧美日韩国产色视频| 日韩在线免费高清视频| 成人中文字幕在线观看| 欧美激情精品久久久久| 国产日韩欧美电影在线观看| 精品亚洲一区二区三区在线播放| 精品欧美激情精品一区| 国产精品自拍小视频| 国产精品久久久久久久av电影| 热re91久久精品国99热蜜臀| 高清日韩电视剧大全免费播放在线观看| 欧美日韩在线观看视频小说| 成人黄色网免费| 亚洲精品国产免费| 国产一区二区三区中文| 91视频免费在线| 91在线观看欧美日韩| 亚洲午夜未满十八勿入免费观看全集| 欧美性受xxxx白人性爽| 92版电视剧仙鹤神针在线观看| 91精品久久久久久久久不口人| 欧美日韩国产精品专区| 北条麻妃久久精品| 日韩精品在线观看一区|