青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

S.l.e!ep.¢%

像打了激速一樣,以四倍的速度運轉,開心的工作
簡單、開放、平等的公司文化;尊重個性、自由與個人價值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

Coroutines in C

Posted on 2013-05-19 16:07 S.l.e!ep.¢% 閱讀(910) 評論(1)  編輯 收藏 引用 所屬分類: C++

引用: http://www.oschina.net/translate/coroutines-in-c

我們都知道構建一個大型的程序是一件非常困難的工作。一個經常面臨的問題來自于此:如果你有一個代碼片段用來生產數據,另一個代碼片段來調用它,哪一個是調用者,哪一個是被調用者呢?

這里有一個簡單的解壓過程的代碼片段和一個同樣簡單的解析代碼片段:

/* Decompression code */
    while (1) {
        c = getchar();
        if (c == EOF)
            break;
        if (c == 0xFF) {
            len = getchar();
            c = getchar();
            while (len--)
                emit(c);
        } else
            emit(c);
    }
    emit(EOF);
/* Parser code */
    while (1) {
        c = getchar();
        if (c == EOF)
            break;
        if (isalpha(c)) {
            do {
                add_to_token(c);
                c = getchar();
            } while (isalpha(c));
            got_token(WORD);
        }
        add_to_token(c);
        got_token(PUNCT);
    }


繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

每個代碼片段都很簡單,通俗易懂。一個通過調用emit()方法每次產生一個字節;另一個調用getchar()每次獲取一個字節。如果只通過調用emit()和getchar()就能彼此間提交數據,那么就能簡單的把兩個代碼片段連接到一起,因此也就能把解壓的輸出直接傳遞到解析部分。

在很多現在操作系統中,你可以使用管道在兩個進程間通信或者兩個線程emit()——在解壓代碼中寫入管道,在解析代碼中使用getchar()從另一端相同的管道中讀取。簡單強壯,但是也重量不可移植。通常你不想把你的一個簡單程序分到線程中。

在這篇文章中我提供了一個創造性的解決方案來處理這個構造的問題。
繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

重寫

常見的方式是重寫末端的通信管道,把它作為一個可調用的函數。這有個簡單的例子。

int decompressor(void) {
    static int repchar;
    static int replen;
    if (replen > 0) {
        replen--;
        return repchar;
    }
    c = getchar();
    if (c == EOF)
        return EOF;
    if (c == 0xFF) {
        replen = getchar();
        repchar = getchar();
        replen--;
        return repchar;
    } else
        return c;
}
void parser(int c) {
    static enum {
        START, IN_WORD
    } state;
    switch (state) {
        case IN_WORD:
        if (isalpha(c)) {
            add_to_token(c);
            return;
        }
        got_token(WORD);
        state = START;
        /* fall through */

        case START:
        add_to_token(c);
        if (isalpha(c))
            state = IN_WORD;
        else
            got_token(PUNCT);
        break;
    }
}

當然你不需要兩個都重寫;只修改一個就可以。如果把解壓片段重寫為以上形式,那么每次調用它都返回一個字符,原始的解析代碼片段中就可以把調用getchar()改為調用decompressor(),這樣程序看起來舒心多了。相反的,如果把解析代碼重寫為上面的形式,那么每次調用都會輸入一個字符,原始的解壓代碼中可以通過調用parser()替換掉了調用emit()。如果你是個貪婪的人你可以把兩個函數都重寫,都作為被調用者。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

這就是我的真實觀點。和兩個重寫后的函數相比原始的代碼丑陋無比,在這里當兩個進程被寫為調用者而不是被調用者時更易讀了。如果你試圖通過解析器推斷出語法,或者通過解壓器了解解壓數據格式,只需閱讀下代碼就可以,同時你會發現原始的代碼清晰些,重寫后的代碼格式上不太清晰,如果沒有嵌入另外一者的代碼這會更好些。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

Knuth協程

在《計算機程序設計藝術》中,Donald Knuth提供了一個解決這類問題的方法。他的方法是徹底丟掉堆棧的概念,不要再想一個進程作為調用者,另一個作為被調用者,把他們當做平等的協作者關系。

實際上就是:把傳統的“調用”稍微改為一個不同的方式。新的“調用”將在某個地方保存返回值而不是堆棧上,并且還能跳轉到另一個保存返回值的指定位置上。因此,解碼器每次生成一個字符,就保存它的程序計數器并且跳轉到上次解析器的位置-解析器每次都需要一個新的字符,它保存自己的程序計數器并且跳轉到上次解碼器的位置。程序可以在兩個函數之間來回自如的傳遞需要的數據了。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

理論上看起來很美,但實際中你卻只能在匯編語言中使用,因為通用的高級語言沒有一個支持調用原始的協程。像類似于C的都是依賴于基礎的堆棧結構,因此當在函數間進行數據傳遞時,一個必須作為調用者,領一個必須作為被調用者。所以如果你想寫可移植的代碼,這種技術和Unix管道一樣不切實際。
繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

基于棧的協程

我們真正想要的是在C中模仿Knuth協程調用原語的能力。我們必須接受這個現實,在C語言中,一個函數將作為調用者,另一個作為被調用者。對于調用者我們沒有任何問題;我們按照原始算法寫代碼就可以,無論什么時候,如果它生成一個字符那么它就需要調用另一個函數。

被調用者有很多問題。對于被調用者,我們想要一個函數,該函數具有“返回并繼續”的操作:從函數返回后,當再次調用它,只需要在上次位置繼續執行就可以。比如,我們希望寫這樣一個函數

int function(void) {
    int i;
    for (i = 0; i < 10; i++)
        return i;   /* won't work, but wouldn't it be nice */
}

連續調用這個函數10次,返回0-9的數字。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

我們怎么能實現這個呢?好吧,我們可以用一個goto語句將控制跳轉到這個函數中的任何一點。所以如果我們有一個狀態變量,我們可以這樣做:

int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: goto LABEL0;
        case 1: goto LABEL1;
    }
    LABEL0: /* start of function */
    for (i = 0; i < 10; i++) {
        state = 1; /* so we will come back to LABEL1 */
        return i;
        LABEL1:; /* resume control straight after the return */
    }
}
這個方法可以奏效。在一些我們可能需要恢復控制的地方,我們擁有一組標簽:一個在開始位置,其他的緊跟著每個返回語句。我們有一個保留在函數調用之間的狀態變量,它指明了下一步我們需要恢復控制那個標簽。在任何返回之前,我們會更新狀態變量到正確的標簽位;任何調用之后,我們會對這個變量的值做一個switch操作來查看它當前進行到哪里。
jimmyjmh
jimmyjmh
翻譯于 1個月前

0人頂

翻譯的不錯哦!

雖然這樣,它還是比較丑。最糟糕的的部分是,這一組標簽必須手動維護,并且在函數體和初始的switch語句之間保持一致。每次新增一個返回語句,我們必須引進一個新表簽名并把它加到switch列表中;而每次刪除一個返回語句,我們又必須移除相應的標簽。剛剛我們只是考慮一個因素增加的兩倍維護工作量。
jimmyjmh
jimmyjmh
翻譯于 1個月前

0人頂

翻譯的不錯哦!

達夫設備(Duff's Device)

C語言中著名的"達夫設備"利用case語句在其匹配switch語句子塊中也是合法的這一事實。Tom Duff利用這個方法優化輸出回路:

switch (count % 8) {
        case 0:        do {  *to = *from++;
        case 7:              *to = *from++;
        case 6:              *to = *from++;
        case 5:              *to = *from++;
        case 4:              *to = *from++;
        case 3:              *to = *from++;
        case 2:              *to = *from++;
        case 1:              *to = *from++;
                       } while ((count -= 8) > 0);
    }
我們可以稍加變動將它應用到協同程序技巧上。我們可以用switch語句本身執行跳轉,而不是用它來確定跳到哪里去執行。
int function(void) {
    static int i, state = 0;
    switch (state) {
        case 0: /* start of function */
        for (i = 0; i < 10; i++) {
            state = 1; /* so we will come back to "case 1" */
            return i;
            case 1:; /* resume control straight after the return */
        }
    }
}
現在這看起來更理想了。我們現在需要做的只是構造一些精確宏,并且可以把細節隱藏到這些似是而非的定義里:
#define crBegin static int state=0; switch(state) { case 0:
#define crReturn(i,x) do { state=i; return x; case i:; } while (0)
#define crFinish }
int function(void) {
    static int i;
    crBegin;
    for (i = 0; i < 10; i++)
        crReturn(1, i);
    crFinish;
}
jimmyjmh
jimmyjmh
翻譯于 1個月前

0人頂

翻譯的不錯哦!

這差不多就是我們想要的了。我們可以通過這種一返回就控制下一個調用回復的方式,用crReturn從函數返回。當然,我們必須遵守一些基本規則(用crBegin和crFinish包住函數體;聲明所需的所有保存在acrReturn中的靜態局部變量;絕不要在一個顯式switch語句中設置一個crReturn);但那些并不會限制我們太多。

剩下的唯一問題是傳給crReturn的第一個參數。就像在上一節引進一個新標簽一樣,我們必須避免與已存在的標簽名沖突,確保所有給crReturn的狀態參數都是不同的。這影響是相當小的 -- 編譯器會抓住它并并不讓它在運行時出錯 -- 但我們還是要避免這樣做。

jimmyjmh
jimmyjmh
翻譯于 1個月前

0人頂

翻譯的不錯哦!

雖然這可以解決,ANSI C還是提供了擴展到當前行號的專門的宏名:__LINE__,因此我們可以把crReturn重寫成:

#define crReturn(x) do { state=__LINE__; return x; \
                         case __LINE__:; } while (0)
而且只要遵守第四條基本規則,我們就不再需要擔心這些狀態參數了(決不在同一行寫兩個crReturn語句)。
jimmyjmh
jimmyjmh
翻譯于 1個月前

0人頂

翻譯的不錯哦!

評估

現在我們有了這個畸形的東西,讓我們來重寫下原始的代碼片段吧。

int decompressor(void) {
    static int c, len;
    crBegin;
    while (1) {
        c = getchar();
        if (c == EOF)
            break;
        if (c == 0xFF) {
            len = getchar();
            c = getchar();
            while (len--)
	        crReturn(c);
        } else
	    crReturn(c);
    }
    crReturn(EOF);
    crFinish;
}
void parser(int c) {
    crBegin;
    while (1) {
        /* first char already in c */
        if (c == EOF)
            break;
        if (isalpha(c)) {
            do {
                add_to_token(c);
		crReturn( );
            } while (isalpha(c));
            got_token(WORD);
        }
        add_to_token(c);
        got_token(PUNCT);
	crReturn( );
    }
    crFinish;
}

我們已經重寫了解碼器和解析器都作為了被調用者,不需要像最后一次那樣大規模的重寫。每個函數的結構恰好是原始結構的鏡像,讀者能夠通過解析器推斷出相應的語法,或者通過解碼器了解到解壓數據格式,比起讀那些狀態機代碼簡單多了。一旦你適應了新的格式,你就會發現控制流程非常直觀:當解壓器產生一個字符,它就調用crReturn將這個字符傳給調用函數,并等待當需要下一個字符時再次被調用。當解析器需要一個新字符時,它通過crReturn返回,并等待下一次被調用時通過參數c傳入新的字符。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

這里代碼里還有一個小的結構變化:parser()中getchar()放在了循環的結尾而不是開頭了,這是因為當進入函數時,第一個字符已經通過c傳進來了。我們應該可以接受這個小的結構改變,或者如果我們真的對此抱有輕盈態度,我們可以認為在parse()傳入數據前,需要一次“初始化”調用。

當然像前面一樣,我們不必把兩個函數都使用協程的宏重寫,修改一個足矣;另一個可以作為他的被調用者。

我們已經取得了我們想要達到的目標:一個可移植的ANSI C在生產者和消費者之間傳遞數據,而不用狀態機重寫代碼。我們已經通過把一個switch語句的生僻的功能結合C的預處理創建一個隱式的狀態機。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

編碼規范

當然,這種方式違背了書上的編碼規范,在公司代碼中嘗試這樣使用你會受到嚴厲的警告!在宏定義中,你的大括號沒有完全匹配,子區塊中使用了case,至于crReturn宏中內容不完整···使用這種不負責任的編碼實踐你沒有被解雇真是一種奇跡。你應該感到自行慚愧。

我需要聲明下編碼規范在這里不適用。在這篇文章里我展示的例子不是很長,也不復雜,即使使用狀態機重寫也能看懂。但是隨著函數變長,重寫的難度越來愈大,并且失去了代碼清晰度,越來越糟糕。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

考慮下,如果一個函數有下面的代碼塊構成

case STATE1:
    /* perform some activity */
    if (condition) state = STATE2; else state = STATE3;

對于讀者來說從函數建立下面的小模塊也不是很難

LABEL1:
    /* perform some activity */
    if (condition) goto LABEL2; else goto LABEL3;

一個調用者,一個被調用者。是的,這兩個函數在可視結構上是一樣的,它們提供的底層算法都非常少。因為使用協程宏想要解雇你的人同樣會因為你寫的連接goto語句的小塊函數而怒斥你!這次它們做對了,因為這樣的函數布局破壞了算法的結構。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

編程規范目標就是為了清晰。像switch,return和case語句把這些重要的東西隱藏到“不清晰”的宏中,從編程標準角度來看你已經破壞了程序的語法結構,違背了代碼清晰的要求。但是我們這樣做是為了突出程序的算法結構,而算法結構也正好是讀者想要了解的!

任何的編碼規范堅持語法的清晰度而犧牲了算法的清晰度都應該被重寫。如果你的老板因為使用這個技巧而解雇你,當安保人員把你拖走時要不斷告訴他們。

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

風騷般的編碼

在正兒八經的應用程序中,協程很少使用,因為它依賴于靜態變量,因此不能重入或者支持多線程。理想情況下,在真實應用程序中,你可能想在多個不同的上下文中調用同一個函數,并且在給定的一個上下文中每次調用時,都能在這個上下文中上一次返回的位置繼續執行。

這很容易做到,我們增加一個新的函數參數——一個上下文結構的指針;我們將所有的局部變量和協程用到的狀態變量都聲明成結構體中的元素.

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

這樣寫丑了點,因為突然間你不得不使用ctx->i作為循環計數器而不是之前使用的i;事實上所有重要的變量都成了協程上下文結構體中的元素。但是這消除了重入的問題,并且沒有影響到程序的結構。

(當然,要是C有Pascal語言的with語句,我們就可以將這個間接的引用隱藏掉,但是很遺憾沒有這些。對于C++語言,我們可以把協程的兩個函數設計成類的成員函數,所有的局部變量設計成類的成員變量,從而將作用域的問題隱藏掉)

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

這兒包含的C語言頭文件實現了一套預定義的協程使用的宏。在文件中定義了二套宏函數,前綴分別是scr和ccr。scr宏是一套簡單的實現,用于可以使用靜態變量的情況;ccr宏更高級一些,能支持重入。在頭文件的注釋中有完整的說明。

需要注意的是,VC++ 6并不喜歡這種協程技巧,因為其默認的debug狀態(Program Database for Edit and Continue)對__LINE__宏的支持有點兒怪。如果想用VC++ 6編譯一個使用了協程的宏,你就要關掉Edit and Continue。(在project settings中,選擇c/c++標簽,在General中,將Debug info改為Program Database for Edit and Continue之外的其他值)。

(這個頭文件是MIT許可的,所以你可以任意使用,沒有任何限制。如果你發現MIT對你的使用方式有限制,可以給我發郵件,我會考慮給你授權。)

使用 這個鏈接 獲得coroutine.h。

感謝您的閱讀。分享才有快樂!

繆斯的情人
繆斯的情人
翻譯于 1個月前

0人頂

翻譯的不錯哦!

參考文獻

  • Donald Knuth,計算機編程藝術, 卷 1. Addison-Wesley, ISBN 0-201-89683-4. Section 1.4.2 describes coroutines in the "pure" form.
  • http://www.lysator.liu.se/c/duffs-device.html 是 Tom Duff's自己關于Duff策略的討論。注意,右下角,暗示著Duff有可能獨立的完成這樣的協程技巧或者其他類似的東西。

    更新與, 2005-03-07: Tom Duff 在一篇博客評論中證實了這一點。“revolting way to use switches to implement interrupt driven state machines”這篇文章中和他最初在郵件中說的確實一樣.

  • PuTTY是一個 Win32 Telnet 和 SSH 客戶端。SSH協議其中一部分使用了協程技巧.,據我所知,這是一個在生產代碼中最糟糕的C代碼片段的輪子。

Feedback

# re: Coroutines in C  回復  更多評論   

2013-05-20 13:04 by zgpxgame
mark
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            午夜精品福利在线| 欧美怡红院视频一区二区三区| 国产婷婷色一区二区三区| 91久久久一线二线三线品牌| 国产一区二区三区免费在线观看 | 欧美xx视频| 欧美亚洲网站| 欧美特黄一级大片| 亚洲国产成人高清精品| 国产在线视频欧美一区二区三区| 一区二区动漫| 一区二区三区欧美| 免费久久久一本精品久久区| 久久久噜噜噜久久中文字免| 国产农村妇女精品一二区| 国产自产精品| 午夜精品成人在线视频| 亚洲欧美资源在线| 国产精品区一区二区三区| 亚洲精品你懂的| 日韩视频永久免费观看| 亚洲激情午夜| 亚洲精品日本| 亚洲永久字幕| 久久国内精品视频| 国产一区二区三区免费在线观看| 亚洲欧美一区二区在线观看| 欧美亚洲综合久久| 国产日韩欧美三级| 久久精品国产99国产精品澳门| 久久久一本精品99久久精品66| 狠狠88综合久久久久综合网| 久久精品国产77777蜜臀| 久久综合中文字幕| 亚洲国产日韩欧美在线图片| 欧美福利视频一区| 一本色道久久88亚洲综合88| 亚洲一二三区精品| 国产精品私房写真福利视频| 欧美在线观看网址综合| 免费观看日韩av| 亚洲美女视频| 国产精品成人av性教育| 午夜精品亚洲| 欧美11—12娇小xxxx| 亚洲精品一区在线观看香蕉| 欧美午夜影院| 久久精品国产99国产精品| 欧美大片免费观看在线观看网站推荐| 日韩视频在线一区二区| 国产精品www网站| 欧美中文字幕在线| 亚洲国产精品电影| 性欧美8khd高清极品| 禁断一区二区三区在线| 欧美激情综合五月色丁香小说| 一本色道久久综合精品竹菊 | 久久九九久久九九| 在线播放日韩专区| 欧美日韩免费区域视频在线观看| 亚洲欧美网站| 亚洲精品偷拍| 久久躁日日躁aaaaxxxx| 亚洲视频在线观看网站| 国内精品**久久毛片app| 欧美二区在线播放| 欧美在线高清| 一本到12不卡视频在线dvd| 久久手机免费观看| 亚洲视屏在线播放| 亚洲国产精品久久| 国产日韩精品电影| 欧美精品自拍| 久久亚洲春色中文字幕| 亚洲一区视频在线观看视频| 亚洲国产精品第一区二区| 久久精品主播| 亚洲欧美国产精品专区久久| 亚洲三级免费观看| 国产一区三区三区| 国产精品嫩草影院av蜜臀| 欧美精品激情| 久久综合一区| 久久精品一区二区国产| 亚洲免费网站| 一本色道久久加勒比88综合| 亚洲第一网站| 免费精品视频| 久久香蕉国产线看观看av| 午夜精品在线| 亚洲综合清纯丝袜自拍| 一区二区三区四区五区精品视频| 亚洲国产精品嫩草影院| 激情成人综合网| 国产视频不卡| 国产日韩欧美在线| 国产精品视频专区| 国产精品久久| 国产精品免费一区豆花| 国产精品成人一区二区| 欧美日韩国产小视频| 欧美母乳在线| 欧美乱在线观看| 欧美女同在线视频| 欧美日韩少妇| 欧美调教vk| 国产精品免费视频观看| 国产伦精品一区二区三区免费| 国产精品99一区二区| 国产精品红桃| 国产欧亚日韩视频| 国产一区二区三区无遮挡| 国产专区综合网| 伊人久久大香线蕉av超碰演员| 激情久久一区| 亚洲激情小视频| 一区二区黄色| 午夜精品久久久久久久白皮肤| 亚洲欧美不卡| 久久久精品动漫| 玖玖精品视频| 亚洲福利视频三区| 亚洲乱码视频| 午夜精品久久久久久久男人的天堂 | 国产亚洲精品资源在线26u| 国产一区久久| 亚洲人体一区| 亚洲午夜未删减在线观看| 亚洲欧美另类在线观看| 久久精品成人一区二区三区蜜臀 | 国产深夜精品福利| 狠狠色综合网站久久久久久久| 在线色欧美三级视频| 国产精品高清网站| 国产一级久久| 亚洲精选大片| 欧美夜福利tv在线| 欧美不卡在线视频| 亚洲深夜av| 久久久综合网站| 欧美午夜不卡在线观看免费 | 午夜激情综合网| 久久久久亚洲综合| 欧美日韩一区二区三区在线观看免 | 久热精品视频在线观看| 亚洲精品日韩欧美| 欧美一区在线看| 欧美日韩国产欧美日美国产精品| 国产啪精品视频| 一本色道**综合亚洲精品蜜桃冫| 欧美在线视频二区| 亚洲三级视频| 久久成人羞羞网站| 国产精品xxxxx| 亚洲国产成人精品久久久国产成人一区 | 欧美视频专区一二在线观看| 在线观看91精品国产麻豆| 亚洲永久精品大片| 欧美激情第3页| 先锋影音国产一区| 欧美日韩免费一区二区三区视频| 国产一区二区三区久久久| 在线视频精品| 欧美激情综合色| 欧美在线免费看| 国产精品你懂的在线| 日韩视频免费观看高清完整版| 久久久久久久999| 亚洲一区二区免费看| 欧美片在线观看| 亚洲国产天堂久久综合网| 久久精品国产亚洲高清剧情介绍| 日韩西西人体444www| 欧美福利电影网| 亚洲欧洲日产国码二区| 久久久久久一区二区| 亚洲欧美激情视频在线观看一区二区三区 | 日韩一级精品| 欧美成人有码| 久久天堂国产精品| 狠狠色综合网| 久久久久一区二区三区四区| 亚洲男人影院| 国产精品一区二区欧美| 午夜精品福利视频| 亚洲视频一区在线观看| 国产精品99免视看9| 亚洲自拍三区| 中日韩在线视频| 国产精品久久久久久久9999| 亚洲图片自拍偷拍| 夜色激情一区二区| 国产精品久久久久久久久久妞妞| 亚洲视频在线观看视频| 日韩视频永久免费| 国产精品videossex久久发布| 亚洲在线免费观看| 亚洲欧美卡通另类91av | 在线激情影院一区| 男男成人高潮片免费网站|