四虎精品视频-四虎精品成人免费网站-四虎黄色网-四虎国产视频-国产免费91-国产蜜臀97一区二区三区

老趙談IL(3):IL可以看到的東西,其實大都也可以用C#來發(fā)現(xiàn)

  在上一篇文章中,我們通過一些示例談論了IL與CLR中的一些特性。IL與C#等高級語言的作用類似,主要用于表示程序的邏輯。由于它同樣了解太多CLR中的高級特性,因此它在大部分情況下依舊無法展現(xiàn)出比那些高級語言更多的CLR細節(jié)。因此,如果您想要通過學習IL來了解CLR,那么這個過程很可能會“事倍功半”。因此,從這個角度來說,老趙并不傾向于學習IL。不過嚴格說來,即使IL無法看出CLR的細節(jié),也不足以說明“IL無用”——這里說“無用”自然有些夸張。但是,如果我們還發(fā)現(xiàn),那些原本被認為需要通過IL挖掘到的東西,現(xiàn)在都可以使用更好的方法來獲得,并且可以起到“事半功倍”的效果,那么似乎我們真的沒有太多理由去追逐IL了。

  在這篇文章中,我們使用最多的工具便是.NET Reflector,從.NET 1.x開始,.NET Reflector就是一個探究.NET框架(主要是BCL)內部實現(xiàn)的有力工具,它可以把一個程序集高度還原成C#等高級語言的代碼。在它的幫助下,幾乎所有程序集實現(xiàn)都變得一目了然,這大大方便了我們的工作。老趙對此深有感觸,因為在某段不算短的時間內,我使用.NET Reflector閱讀過的代碼數(shù)量遠遠超過了自己編寫的代碼。與此相反的是,老趙幾乎沒有使用IL探索過.NET框架下的任何問題。這可能還涉及到方式方法和個人做事方式,但是如果這真有效果的話,為什么要舍近求遠呢?希望您看過了這篇文章,也可以像我一樣擺脫IL,投入.NET Reflector的懷抱。

示例一:探究語言細節(jié)

  C#語言從1.0到3.0版本的進化過程中,大部分新特性都是依靠編譯器的魔法。就拿C#3.0的各種新特性來說,Lambda表達式,LINQ,自動屬性等等,完全都是基于CLR 2.0中已有的功能,再配合新的C#編譯器而產生的各種神奇效果。有些朋友認為,掌握IL之后便把握了.NET的根本,以不變應萬變,只要讀懂IL,那么這些新特性都不會對您形成困擾。這話說的并沒有錯,只是老趙認為,“掌握IL”在這里只是一個“充分條件”而不是一個“必要條件”,我們完全可以使用.NET Reflector將程序集反編譯成C#代碼來觀察這些。

  這里我們使用.NET Reflector來觀察最最常見,最最普通的foreach關鍵字的功能。我們都知道foreach是遍歷一個IEnumerble對象內元素的方式,我們也都知道foreach其實是GoF Iterator模式的實現(xiàn),通過MoveNext方法和Current屬性進行配合共同完成。不過大部分朋友似乎都是從IL進行觀察,或是“聽別人說”而了解這些的。事實上,.NET Reflector也可以很容易地證實這一點,只是這中間還有些“特別”的地方。那么首先,我們還是來準備一個最簡單的foreach語句:

static void DoEnumerable(IEnumerable<int> source){    foreach (int i in source)    {        Console.WriteLine(i);    }}

  如果觀察它的IL代碼,即使不了解IL的朋友也一定可以看出,其中涉及到了GetEnumerator,MoveNext和Current等成員的訪問:

.method private hidebysig static void DoEnumerable(    class [mscorlib]System.Collections.Generic.IEnumerable`1 source) cil managed{    .maxstack 1    .locals init (        [0] int32 i,        [1] class [mscorlib]System.Collections.Generic.IEnumerator`1 CS$5$0000)    L_0000: ldarg.0     L_0001: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1        [mscorlib]System.Collections.Generic.IEnumerable`1::GetEnumerator()    L_0006: stloc.1     L_0007: br.s L_0016    L_0009: ldloc.1     L_000a: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1::get_Current()    L_000f: stloc.0     L_0010: ldloc.0     L_0011: call void [mscorlib]System.Console::WriteLine(int32)    L_0016: ldloc.1     L_0017: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()    L_001c: brtrue.s L_0009    L_001e: leave.s L_002a    L_0020: ldloc.1     L_0021: brfalse.s L_0029    L_0023: ldloc.1     L_0024: callvirt instance void [mscorlib]System.IDisposable::Dispose()    L_0029: endfinally     L_002a: ret     .try L_0007 to L_0020 finally handler L_0020 to L_002a}

  但是,如果使用.NET Reflector觀察它的C#代碼又會如何呢?

private static void DoEnumerable(IEnumerable source){    foreach (int i in source)    {        Console.WriteLine(i);    }}

  請注意,以上這段是由.NET Reflector從IL反編譯后得到的C#代碼,這簡直……不是簡直,是完完全全真真正正地和我們剛才寫的代碼一模一樣!這就是.NET Reflector的強大之處,由于它意識到IL調用了IEnumerable.GetEnumerator方法,因此它就“留心”判斷這個調用的“模式”是否符合一個標準的foreach,如果是,那么就會顯示為一個foreach語句而不是while...MoveNext。不過,這難道不就掩蓋了“事物本質”了嗎?要知道我們的目標可是探究foreach的形式,既然.NET Reflector幫不了的話,我們不還是需要去觀察IL嗎?

  剛才老趙提到,.NET Reflector在判斷IL代碼時發(fā)現(xiàn)一些標準的模式時會進行代碼“優(yōu)化”。那么我們能否讓.NET Reflector不要做這種“優(yōu)化”呢?答案是肯定的,只是需要您在.NET Reflector中進行一些簡單的設置:

  打開View菜單中的Options對話框,在左側Disassembler選項卡中修改Optimization級別,默認很可能是.NET 3.5,而現(xiàn)在我們要將其修改為None。這么做會讓.NET Reflector最大程度地“直接”翻譯IL代碼,而不做一些額外優(yōu)化。將Optimization級別設為None以后,DoEnumerable方法的代碼就變?yōu)榱耍?/p>

static void DoEnumerable(IEnumerable<int> source){    int num;    IEnumerator<int> enumerator;    enumerator = source.GetEnumerator();Label_0007:    try    {        goto Label_0016;    Label_0009:        num = enumerator.Current;        Console.WriteLine(num);    Label_0016:        if (enumerator.MoveNext() != null)        {            goto Label_0009;        }        goto Label_002A;    }    finally    {    Label_0020:        if (enumerator == null)        {            goto Label_0029;        }        enumerator.Dispose();    Label_0029: ;    }Label_002A:    return;}

  這是C#代碼嗎?為什么會有那么多的goto?為什么MoveNext方法返回的布爾值可以和null進行比較?其實您把這段代碼復制粘貼后會發(fā)現(xiàn),它能夠正常編譯通過,效果也和剛才的foreach語句完全一樣。這就是去除“優(yōu)化”的效果。老趙在上一篇文章中談到說:IL和C#一樣,都是用于表現(xiàn)程序邏輯。C#使用if...else、while、for等等豐富語法,而在IL中就會變成判斷+跳轉語句。上面的C#代碼便直接保留了IL的這個“特性”。不過還好,我們還是可以看出try...finally,可以看出MoveNext方法和Current屬性的訪問,可以看到程序使用Console.WriteLine輸出數(shù)據(jù)。至此,我們便發(fā)現(xiàn)了foreach語句的真面目。從現(xiàn)在開始,在您準備深入IL之前,老趙也建議您可以嘗試一下使用None Optimization來觀察C#代碼。

  實事求是地說,上面的C#代碼的“轉向邏輯”并不那么清晰,因此您在理解的時候可以把它復制到編輯器中,進行一些簡單調整。但是從老趙的經驗上來看,需要使用None Optimization進行探索的地方非常少見。foreach是一個,還有便是C#中的其他一些“別名”,如使用using關鍵字管理IDisposable對象,以及l(fā)ock關鍵字。而且,其實這段邏輯也只是沒有優(yōu)化IL中的跳轉語句而已,已經比IL本身要直觀許多了。此外,關于對象創(chuàng)建,變量聲明,方法調用,屬性訪問,事件加載……一切的一切都還是最常用的C#代碼。因為還是那個原因:從大部分情況上來看,IL也只是表現(xiàn)了程序邏輯,并沒有比C#等語言體現(xiàn)出更多的細節(jié)。

  我在這里舉了一個較為極端的例子,因為我發(fā)現(xiàn)不少朋友并沒有嘗試過使用None Optimization來觀察過代碼。這里也可以看出,.NET Reflector的“優(yōu)化級別”還不夠“細致”。不過這應該是一個“產品設計”的正常結果,因為foreach/using/lock的關鍵字都是從.NET 1.0誕生伊始就存在的,也就是說,即使.NET Reflector選擇將IL編譯為C# 1.0,它的表現(xiàn)形式依舊是“標準模式”,這方面可能就不能過于強求了吧。至于其他一些探索,例如C#中的自動屬性,Lambda表達式構建表達式樹或匿名委托,乃至C# 4.0中的dynamic關鍵字,都是使用.NET 3.5 Optimization進行探索便可得知的結果。您可以回憶一下自己看過的文章,其中有多少是使用IL解釋問題的呢?

示例二:學習.NET平臺上的其他語言

  在.NET平臺上,任何語言都會先編譯為IL,然后再運行時由JIT轉化為機器碼。因此有種說法是,只要把握了IL,.NET平臺上各種語言之間的遷移都會變得容易。對此老趙有不同看法。在以前討論語言是否重要的時候,老趙提到,語言它并不僅僅是一種文字表現(xiàn)形式,而是一種“思維方式”的改變,這可能會影響到您程序的編碼風格,API設計乃至架構(這個鏈接可能打不開,因為……)。實際上,如果您只是在C#與VB.NET之間進行遷移,原本就是一件相當容易的事情,因為它們之間“語言”的各種概念和特性都非常接近。而一種改變您思維的語言,才是真正有價值,而且值得進行比較和探索的。如果一味地追求“把握本源”,那么甚至還有比IL更低抽象的事務,但這些就已經違背了“創(chuàng)造一門語言”,以及您學習它的目的了,不是嗎?

  當然,探索也是需要的,尤其是.NET平臺上的各種語言,他們被統(tǒng)一在同樣的平臺上,這本身就是一種很好的資源。這種資源就是所謂的“比較學習”。您可以把新的語言和您熟悉的語言進行對比,吸收其中的長處(如優(yōu)秀的思維方式),這樣便可以更好地使用舊有語言。例如,您把F#類庫轉化為C#代碼進行觀察之后,發(fā)現(xiàn)其中大量函數(shù)式編程風格的API是使用“委托”來實現(xiàn)的,您可能就會想到是否可以設計出函數(shù)式編程風格的C# API,是否可以把F#中List或Seq模塊中的各種高階函數(shù)移植到您自己的項目中來。這就有了更好的價值,這價值也不僅僅只是您“學會了新的語言”。

  例如,我們現(xiàn)在使用尾遞歸來計算斐波那契數(shù)列。在之前的文章中,我們的作法是:

private static int FibTail(int n, int acc1, int acc2){    if (n == 0) return acc1;    return FibTail(n - 1, acc2, acc1 + acc2);}public static int Fib(int n){    return FibTail(n, 0, 1);}

  為了“尾遞歸”,我們必須定義一個私有的FibTail方法,接收三個參數(shù)。而對外的接口還是一個公有的Fib方法,它返回斐波那契數(shù)列第n項的結果。這個示例很簡單,作法也沒有任何問題。但是老趙有時候會覺得,我們?yōu)槭裁捶且x一個額外的“輔助方法”,然后在現(xiàn)有的方法里只是進行一個簡單的轉發(fā)?如果這個輔助方法會在其他地方得到調用也就罷了(我們遵守了DRY原則),但是現(xiàn)在卻有點“平白無故”地在代碼里增加了一個方法,這樣在VS的Class View或編輯器上方的下拉列表中也會多出一項。此外,為了表示兩個方法的關系,您可能還會使用region把它們包裹起來……

  不過在F#中,上面的尾遞歸就可以這樣寫:

let fib n =    let rec fibTail x acc1 acc2 =        match x with        | 0 -> acc1;        | _ -> fibTail (x - 1) acc2 (acc1 + acc2)    fibTail n 0 1

  在fib方法內部,我們可以重新定義一個fibTail方法,其中實現(xiàn)了尾遞歸。對于外部來說,只有fib方法是公開的,外界絲毫不知道fibTail方法的存在,這種定義內部函數(shù)的作法在F#中非常常見。而編譯后,我們在.NET Reflector中便可看到與之對應的C#實現(xiàn):

public static int fib(int n){    switch (n)    {        case 0:            return 0;    }    return fibTail@7@7(n - 1, 1, 1);}internal static int fibTail@7@7(int x, int acc1, int acc2){    ...}

  在F#中沒有internal的訪問級別,您可以認為這里internal便是private。于是我們得知(可能您本身也猜得到):由于.NET本身并沒有“嵌套方法”特性,因此在這里編譯器會重新生成一個特殊的私有方法,并且在fib方法里進行調用。于是我們想到,這個“自動生成方法”的特性,在C#中也有體現(xiàn)啊。例如,IEnmuerable有一個擴展方法是Where,我們可以用Lambda表達式構造一個匿名委托作為參數(shù)……唔唔,這不就相當于把一個方法定義在另一個方法內部了嗎?于是,我們修改一下之前C#的尾遞歸的實現(xiàn):

public static int Fib(int n){    Func<int, int, int, int> fibTail = null;    fibTail = (x, acc1, acc2) =>    {        if (x == 0) return acc1;        return fibTail(x - 1, acc2, acc1 + acc2);    };    return fibTail(n, 0, 1);}

  如果沒有F#的“提示”,可能我們只能想到list.Where(i => i % 2 == 0)這種形式的用法,我們平時不會在方法內部額外地“創(chuàng)建一個委托”,然后加以調用,而且還用到了“遞歸”——甚至還是“尾遞歸”(雖然C#編譯器在這里沒有進行優(yōu)化,而且這里其實也只是個“偽遞歸”,因為fibTail其實是個可改變的“函數(shù)指針”)。不過,由于我們剛才通過C#來觀察F#的編譯結果,聯(lián)想到它和我們以前觀察到的C#中“某個特性”非常相似,再加上合理的嘗試,最終同樣得出了一個還算“令人滿意”的使用方式。

  這只是一個示例,我并不是說這種作法是所謂的“最佳實踐”。任何辦法一旦遭到濫用也肯定不會有好處,您要根據(jù)當前情況判斷是否應該采取某種作法。剛才的演示只是為了說明,我們應該如何從其他語言中吸取優(yōu)勢思想,改進我們的編程工作。當然,您使用IL來探索新的語言也沒有太大問題,C#能看到的東西用IL也可以看到。但是請您回想一下,即使您平時學習IL,您想過直接使用IL來寫程序嗎?您學習和探索新語言的目的,只是為了搞清楚它的IL表現(xiàn)形式嗎?為什么您不使用簡單易懂的C#,卻要糾纏于IL中那些紛繁復雜的指令呢?

示例三:性能相關

  學習IL對寫出高性能的.NET程序有幫助嗎?

  記得以前在學習“計算機系統(tǒng)概論”課程時,有一個實驗就是為幾段C程序進行優(yōu)化。當時的手段可謂無所不用其極,例如內聯(lián)一個子過程以避免call指令的消耗,或把一段C代碼使用匯編進行替換等等。從結果上看,它們都能對性能有“明顯”的提高。不過,那些都是為了加深概念而進行的練習,并不是說在現(xiàn)代程序中應該使用這種方式進行優(yōu)化。現(xiàn)在早已不是在“指令級別”進行性能優(yōu)化的時期了,連操作系統(tǒng)內核也只是在一些對性能要求非常高的地方,如內存管理,線程調度中的細微方面使用匯編來編寫,其余部分也都是用C語言來完成。這并不是僅僅是因為“可維護性”等考慮,也有部分原因是因為在目前編譯技術的發(fā)展下,一些極端的做法已經很難產生有效的優(yōu)化效果了(例如一般來說來,程序員寫出的C代碼的性能會優(yōu)于他寫的匯編代碼)。

  此外,在您不知道JIT究竟作了什么事情的情況下,觀察IL這樣一種高度抽象的語言,您還是無法真正判斷出一個程序從微觀上的性能如何。不過這并不是說,現(xiàn)代程序不應該“主動”追究性能,而是說,現(xiàn)代程序在性能優(yōu)化問題上并非如此簡單,它涉及到的東西會更多,需要更加合適的手段。例如,即使您內聯(lián)了一個子過程,也只是減少了call指令的所帶來的消耗,但是這與這個子過程本身“一長串”指令相比,所帶來的提高是微乎其微的。而如果您一旦破壞了Locality或造成了False Sharing,或造成了資源競爭等等,這可能就會造成數(shù)倍甚至更多的性能損耗。換句話說,影響現(xiàn)代應用程序的性能的因素大都是“宏觀”的,用通俗的話來說,一般都是“寫法”上造成的問題。

  這也是為什么說“Make clean code fast”遠比“Make fast code clean”來的容易,現(xiàn)代程序更注重的是“清晰”而并非是“性能”。因為程序清晰,更容易讓人發(fā)現(xiàn)性能瓶頸究竟在何處,可以進行有針對性地優(yōu)化(即使是那種在極端性能要求下故意進行的“丑陋”寫法,也是為了高性能而“丑陋”,而不是因為“丑陋”而高性能,分清這一點很重要)。換句話說,如果我們有一種更清晰地方式來查看同樣的程序實現(xiàn),不也降低了探索程序性能瓶頸的難度嗎?那么,同樣一段程序,您會通過C#進行觀察,還是使用IL呢?

  有朋友可能會說:即使無法把握JIT對于IL的優(yōu)化,但是從IL中可以看出高級語言,如C#的編譯器的優(yōu)化效果啊。這話本沒有錯,但問題還是在于,C#的編譯器優(yōu)化效果,是否在“反編譯”回來之后就無法觀察到了呢?“優(yōu)化過程”往往都是不可逆的,它會造成信息丟失,導致我們很難從“優(yōu)化結果”中看出“原始模樣”,這一點在上一篇文章中也有過論述。換句話說,我們通過C# => IL => C#這一系列“轉化”之后,幾乎都可以清楚地發(fā)現(xiàn)C#編譯器做過哪些優(yōu)化。這里還是使用經典的foreach作為示例,您知道以下兩個方法的性能高低如何?

static void DoArray(int[] source){    foreach (int i in source)    {        Console.WriteLine(i);    }}static void DoEnumerable(IEnumerable<int> source){    foreach (int i in source)    {        Console.WriteLine(i);    }}

  經過了C#編譯器的優(yōu)化,再使用.NET Reflector查看IL反編譯成C#(None Optimization)的結果,就會發(fā)現(xiàn)它們變成了此般模樣:

private static void DoArray(int[] source){    int num;    int[] numArray;    int num2;    numArray = source;    num2 = 0;    goto Label_0014;Label_0006:    num = numArray[num2];    Console.WriteLine(num);    num2 += 1;Label_0014:    if (num2 < ((int)numArray.Length))    {        goto Label_0006;    }    return;}private static void DoEnumerable(IEnumerable<int> source){    int num;    IEnumerator<int> enumerator;    enumerator = source.GetEnumerator();Label_0007:    try    {        goto Label_0016;    Label_0009:        num = enumerator.Current;        Console.WriteLine(num);    Label_0016:        if (enumerator.MoveNext() != null)        {            goto Label_0009;        }        goto Label_002A;    }    finally    {    Label_0020:        if (enumerator == null)        {            goto Label_0029;        }        enumerator.Dispose();    Label_0029: ;    }Label_002A:    return;}

  C#編譯器的優(yōu)化效果表露無遺:對于int數(shù)組的foreach其實是被轉化為類似于for的下標訪問遍歷,而對于IEnumerable還是保持了foreach關鍵字中標準的“while...MoveNext”模式(如果您把Console.WriteLine語句去掉的話,就可以使用.NET 3.5 Optimization直接看出兩者的不同,您不妨一試)。由此看來,DoArray的性能會比后兩者要高。事實上,由于性能主要是由“實現(xiàn)方式”決定的,因此我們可以通過反編譯成C#代碼的方式來閱讀.NET框架中的大部分代碼,IL在這里起到的效果很小。例如在文章《泛型真的會降低性能嗎?》里,Teddy大牛就通過閱讀.NET代碼來發(fā)現(xiàn)數(shù)組的IEnumerable實現(xiàn),為什么性能遠低于IEnumerable。

  不過,判斷兩者性能高低,最簡單,也最直接的方式還是進行性能測試。例如您可以使用CodeTimer來比較DoArray和DoEnumerable方法的性能,一目了然。

  值得一提的是,如果要進行性能優(yōu)化,需要做的事情有很多,而“閱讀代碼”在其中的重要性其實并不高,而且它也最容易誤入歧途的一種。“閱讀代碼”充其量是一種人工的“靜態(tài)分析”,而程序的運行效果是“動態(tài)”的。這篇文章解釋了為什么使用foreach對ArrayList進行遍歷的性能會比List低,其中使用了Profiler來說明問題。Profiler能告訴我們很多難以觀察到的事情,例如在遍歷中究竟是ArrayList哪個方法消耗時間最長。此外它還發(fā)現(xiàn)了ArrayList在遍歷時創(chuàng)建了大量的對象,這種對于內存資源的消耗,幾乎不可能從一小段代碼中觀察得出。此外,不同環(huán)境下,同樣的代碼可能執(zhí)行效果會有不同。如果沒有Profiler,我們可能會選擇把一段執(zhí)行了100遍的代碼性能提升1秒鐘,卻不會把一段執(zhí)行100000遍的代碼性能提升100毫秒。性能優(yōu)化的關鍵是“有的放矢”,如果沒有Profiler幫我們指明道路,做到這一點相當困難。

  其實老趙對于性能方面說的這些,可以大致歸納為以下三點:

  • 關注IL,對于從微觀角度觀察程序性能很難有太大幫助,因為您很難具體指出JIT對IL的編譯方式。
  • 關注IL,對于從宏觀角度觀察程序性能同樣很難有太大幫助,因為它的表述能力不會比C#來的直觀清晰。
  • 性能優(yōu)化,最關鍵的一點是使用Profiler來找出性能瓶頸,有的放矢。

  所以,如果您問老趙:“學習IL,對寫出高性能的.NET程序有幫助嗎?”我會回答:“有,肯定有啊”。

  但是,如果您問老趙:“我想寫出高性能的.NET程序,應該學習IL嗎?”我會回答:“別,別學IL”。

總結

  feilng在前文留下的一些評論,我認為說得非常有道理:

IL只是在CLR的抽象級別上說明干什么,而不是怎么干……重要的是要清楚在現(xiàn)實條件下,需要進入那個層次才能獲取足夠的信息,掌握接口的完整語義和潛在副作用。

  IL的確比C#等高級語言來的所謂“底層”,但是很明顯,IL本身也是一種高級抽象。而即使是機器碼,它也可以說是基于CPU的抽象,CPU上如流水線,并行,內存模型,Cache Lock等東西對于匯編/機器碼來說也可以說是一種“封裝”。從不同層次可以獲得不同信息,我們追求“底層”的目的肯定也不是“底層”這兩個字,而是一種收獲。了解自身需要什么,然后能夠選擇一個合理的層次進入,并得到更好的收益,這本身也是一種能力。追求IL的做法,本身并沒有錯,只是追求IL一定是當前情況下的最優(yōu)選擇嗎?這是一個值得不斷討論的問題,我的這篇文章也只是表達了我個人對某些問題的看法。

 

NET技術老趙談IL(3):IL可以看到的東西,其實大都也可以用C#來發(fā)現(xiàn),轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯(lián)系我們修改或刪除,多謝。

主站蜘蛛池模板: 建设工程监理规范最新版50319-2019| 野兽罪人电影免费观看| 飞哥和小佛| 三峡游轮重庆到宜昌4天三夜| 楚门的世界演员表| 大明宫词演员表全部| 丹尼尔·吉里斯| 猎仇者演员表| 《可爱的小鸟》阅读答案| 我的世界大橙子| 七寸照片| 抖音在线官网| 音乐会电视剧免费观看完整版| 陈昭昭| 红电视剧演员表| ab变频器中文说明书| 色域在线| 100张照片| 电影《迷雾》| 世界轮廓图| 国内性爱视频| 忘记年龄,讨好自己,用自己喜欢的方式生活| 四川旅游攻略| 大内群英 电视剧| 肥皂泡节选阅读理解答案三年级| 草逼啊啊啊| 抖音在线观看| 周柯宇个人资料| 赌侠 1990 刘德华| 奶粉罐回收多少钱一个| 王宝强电影全部作品| 守卫者2| 《父亲的爱》阅读理解答案| 黄视频免费在线播放| 师奶madam 电视剧| 不可饶恕 电影| 大学英语精读4课后答案| 最新电影免费观看| 舞法天女第三季| 每周食品安全排查治理报告表| 不纽扣的女孩|