lua 函數調用1 -- 閉包詳解和C調用

來源:互聯網
上載者:User

標籤:blog   http   io   os   使用   java   strong   for   資料   

這裡, 簡單的記錄一下lua中閉包的知識和C閉包調用

前提知識: 在lua api小記2中已經分析了lua中值的結構, 是一個 TValue{value, tt}組合, 如果有疑問, 可以去看一下

 

一些重要的資料結構

 

    lua中有兩種閉包, c閉包和lua閉包

    兩種閉包的公用部分:

       #define ClosureHeader CommonHeader;     lu_byte isC;        lua_byte nupvalues; GCObject* gclist;       struct Table env

                                                                  /*是否是C閉包*/     /*upval的個數*/                                   /* 閉包的env, set/getenv就是操縱的它 */

 

    C閉包的結構

        struct CClosure{

             ClosureHeader;

             lua_CFunction f;

             TValue upvalue[1];

        }

      結構比較簡單, f是一個滿足 int lua_func(lua_State*) 類型的c函數

      upvalue是建立C閉包時壓入的upvalue, 類型是TValue, 可以得知, upvalue可以是任意的lua類型

 

    Lua閉包結構

       struct LClosure{

           ClosureHeader;

           strcut Proto* p;

           UpVal* upvals[1];

       }

      Proto的結構比較複雜, 這裡先不做分析

 

   統一的閉包結構, 一個聯合體, 說明一個閉包要麼是C閉包, 要麼是lua閉包, 這個是用isC表識出來的.

       union Closure{

            CClosure c;

            LClosure  l;

       }

 

糾結的閉包

       為什麼大家叫閉包, 不叫它函數, 它看起來就是函數啊? 為什麼要發明一個"閉包"這麼一個聽起來蛋疼的詞呢? 我也糾結在這裡好久了, 大概快一年半了吧~~~=.=我比較笨~~~隨著看源碼, 現在想通了, 拿出一些的自己在研究過程中的心得[盡量的通俗易懂]:

       1. c 語言中的函數的定義: 對功能的抽象塊, 這個大家沒什麼異議吧.

       2. lua對函數做了擴充:

              a. 可以把幾個值和函數綁定在一起, 這些值被稱為upvalue.

              ps:  可能有人覺得c++的函數對象也可以把幾個值和函數綁定起來啊, 是這樣的, 但是這個問題就像是"在彙編中也可以實現物件導向呀"一樣, lua從語言層面對upvalue提供了支援, 就像c++/java從語言層面提供了對類, 對象的支援一樣, 當然大大的解放了我們程式員的工作量, 而且配上lua動態類型, 更是讓人輕鬆了不少.

              b. 每個函數可以和一個env(環境)綁定.

              ps:  如果說上面的upvalue還能在c++中coding出來, 那麼env 上下文環境這種動態語言中特有的東西c++就沒有明顯的對應結構了吧? 可能有人覺得lua是c寫的, 通過coding也可以實現, 好吧=.= , "能做和做"是兩碼事, 就想你能步行從北京到上海, 不表明你就必須要這麼做. env是非常重要和有用的東西, 它可以輕鬆創造出一個受限的環境, 就是傳說中的"沙箱", 我說的更通俗一點就是"一個動態名字空間機制". 這個先暫時不分析.

 

       好了, 現在我們看到

            c       函數    { 功能抽象 }

            lua    閉包     {功能抽象, upvalue, env}

            重點: 閉包 == {功能抽象, upvalue, env}

  

       看到這裡, 大家都明白了, 如果把lua中的{功能抽象, upvalue, env}也稱為函數, 不但容易引起大家的誤解以為它就是和c函數一樣, 而且它確實不能很好的表達出lua函數的豐富內涵, 閉包, "閉" 是指的它是一個object, 一個看得見摸得著的東西, 不可分割的整體(first class); "包" 指的是它包含了功能抽象, upvalue, env. 這裡一個很有趣的事實就是, {功能抽象, upvalue, env}是很多動態語言的一個實現特徵, 比如lua, javascript都有實現這樣的結構, 它是先被實現出來, 然后冠以"閉包"這樣一個名稱. 所以, 你單單想去理解閉包這個詞的話, 基本是沒有辦法理解的, 去網上查閉包, 沒用, 你能查到的就是幾個用閉包舉出的例子, 看完以後保證你的感覺是"這玩意挺神秘的, 但是還是不懂什麼是閉包", 為什麼不懂?  因為它指的是一種實現結構特徵, 是為了實現動態語言中的函數first class和上下文概念而創造出來的.

       寧可多說幾句, 只要對加深理解有好處就行, 有這樣兩個個句子"我騎車去買點水果" "我用來閉包{功能抽象, upvalue, env}實現動態語言中的函數first class和上下文概念" , 閉包和"騎車"都是你達到目地的一種手段, 為了買水果你才想了"騎車"這樣一個主意, 並不是為了騎車而去買水果. 只把把眼睛盯在騎車上是不對的, 它只是手段.

 

 

向lua中註冊c函數的過程是通過lua_pushcclosure(L, f, n)函數實現的

 

       流程:  1. 建立一個 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的記憶體, 這段記憶體是 CClosure + TValue[n], 並做gc簿記[這點太重要了, 為什麼lua要控制自己世界中的所有變數, 就是因為它要做gc簿記來管理記憶體],  isC= 1 標示其是一個C閉包.

             2. c->f = f綁定c函數.    ---------  閉包.功能抽象 = f

             3. env = 當前閉包的env[這說明了被建立的閉包繼承了建立它的閉包的環境].  ----------- 閉包.env = env

             4. 把棧上的n個元素賦值到c->upvalue[]數組中, 順序是越先入棧的值放在upvalue數組的越開始位置, c->nupvalues指定改閉包upvalue的個數.  ---------- 閉包.upvalue = upvalue

             5. 彈出棧上n個元素, 並壓入建立的Closure到棧頂.

       整個流程是比較簡單的, 分配記憶體, 填寫屬性, 鏈入gc監控, 綁定c函數, 綁定upvalue, 綁定env一個C閉包就ok了, 請結合上面給的閉包的解釋, 很清楚了.

 

現在來解析這個C閉包被調用的過程[注意, 這裡只涉及C閉包的調用]

 

       lua 閉包調用資訊結構:

            struct CallInfo {
                 StkId base;  /* base for this function */     ---- 閉包調用的棧基
                 StkId func;  /* function index in the stack */  ---- 要調用的閉包在棧上的位置
                 StkId    top;  /* top for this function */     ---- 閉包的棧使用限制, 就是lua_push*的時候得看著點, push太多就超了, 可以lua_checkstack來擴
                 const Instruction *savedpc;      ---- 如果在本閉包中再次調用別的閉包, 那麼該值就儲存下一條指令以便在返回時繼續執行
                 int nresults;  /* expected number of results from this function */   ---- 閉包要返回的值個數
                 int tailcalls;  /* number of tail calls lost under this entry */   ---- 尾遞迴用, 暫時不管
            }

        從注釋就可以看出來, 這個結構是比較簡單的, 它的作用就是維護一個函數調用的有關資訊, 其實和c函數調用的棧幀是一樣的, 重要的資訊base –> ebp, func –> 要調用的函數的棧index, savedpc –> eip, top, nresults和tailcalls沒有明顯的對應.

        在lua初始化的時候, 分配了一個CallInfo數組, 並用L->base_ci指向該數組第一個元素, 用L->end_ci指向該數組最後一個指標, 用L->size_ci記錄數組當前的大小, L->ci記錄的是當前被調用的閉包的調用資訊.

 

        下面講解一個c閉包的調用的過程:

        情景: c 函數 int lua_test(lua_State* L){

                          int a = lua_tonumber(L, 1);

                          int b = lua_tonumber(L, 2);

                          a = a + b;

                          lua_pushnumber(L, a);

                 }

                 已經註冊到了lua 中, 形成了一個C閉包, 起名為"test", 下面去調用它

                 luaL_dostring(L, "c = test(3, 4)")

 

         1. 首先, 我們把它翻譯成對應的c api

             

                                1. 最初的堆棧

 

               lua_getglobal(L, “test”)

               lua_pushnumber(L, 3)

               lua_pushnumber(L, 4)      

             

                              2. 壓入了函數和參數的堆棧

 

               lua_call(L, 2, 1)

             

                               3. 調用lua_test開始時的堆棧

 

             

                               4. 調用結束的堆棧

 

               lua_setglobal(L, “c”)     

             

                               5. 取出調用結果的堆棧

 

        我們重點想要知道的是lua_call函數的過程

            1. lua的一致性在這裡再一次的讓人震撼, 不管是dostring, 還是dofile, 都會形成一個閉包, 也就是說, 閉包是lua中用來組織圖的基本構件, 這個特點使得lua中的結構具有一致性, 是一種簡明而強大的概念.

            2. 根據1, a = test(3, 4)其實是被組織成為一個閉包放在lua棧頂[方便期間, 給這個lua閉包起名為bb], 也就說dostring真正調用的是bb閉包, 然後bb閉包執行時才調用的是test

 

         [儲存當前資訊到當前函數的CallInfo中]

            3. 在調用test的時刻, L->ci記載著bb閉包的調用資訊, 所以, 先把下一個要執行的指令放在L->ci->savedpc中, 以供從test返回後繼續執行.

            4. 取棧上的test C閉包 cl, 用 cl->isC == 1斷定它的確是一個C閉包

 

         [進入一個新的CallInfo, 布置堆棧]

            5. 從L中新分配一個CallInfo ci來記錄test的調用資訊, 並把它的值設定到L->ci, 這表明一個新的函數調用開始了, 這裡還要指定test在棧中的位置, L->base = ci->base = ci->func+1, 注意, 這幾個賦值很重要, 導致的堆棧狀態由圖2轉化到圖3, 可以看出, L->base指向了第一個參數, ci->base也指向了第一個參數, 所以在test中, 我們調用lua_gettop函數返回的值就是2, 因為在調用它的時候, 它的棧幀上只有2個元素, 實現了lua向c語言中傳參數.

 

        [調用實際的函數]

            6. 安排好堆棧, 下面就是根據L->ci->func指向的棧上的閉包(及test的C閉包), 找到對應的cl->c->f, 並調用, 就進入了c函數lua_test

 

        [擷取傳回值調整堆棧, 返回原來的CallInfo]

            7. 根據lua_test的傳回值, 把test閉包和參數彈出棧, 並把傳回值壓入並調整L->top

            8. 恢複 L->base, L->ci 和 L->savedpc, 繼續執行.

        總結: 調用一個新的閉包時 1. 儲存當前資訊到當前函數的CallInfo中 2. 進入一個新的CallInfo, 布置堆棧  3. 調用實際的函數  4. 擷取傳回值調整堆棧, 返回原來的CallInfo

lua 函數調用1 -- 閉包詳解和C調用

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.