国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

這篇具有很好參考價(jià)值的文章主要介紹了深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

棧幀(Stack Frame)是 Python 虛擬機(jī)中程序執(zhí)行的載體之一,也是 Python 中的一種執(zhí)行上下文。每當(dāng) Python 執(zhí)行一個(gè)函數(shù)或方法時(shí),都會(huì)創(chuàng)建一個(gè)棧幀來表示當(dāng)前的函數(shù)調(diào)用,并將其壓入一個(gè)稱為調(diào)用棧(Call Stack)的數(shù)據(jù)結(jié)構(gòu)中。調(diào)用棧是一個(gè)后進(jìn)先出(LIFO)的數(shù)據(jù)結(jié)構(gòu),用于管理程序中的函數(shù)調(diào)用關(guān)系。

棧幀的創(chuàng)建和銷毀是動(dòng)態(tài)的,隨著函數(shù)的調(diào)用和返回而不斷發(fā)生。當(dāng)一個(gè)函數(shù)被調(diào)用時(shí),一個(gè)新的棧幀會(huì)被創(chuàng)建并推入調(diào)用棧,當(dāng)函數(shù)調(diào)用結(jié)束后,對應(yīng)的棧幀會(huì)從調(diào)用棧中彈出并銷毀。

棧幀的使用使得 Python 能夠?qū)崿F(xiàn)函數(shù)的嵌套調(diào)用和遞歸調(diào)用。通過不斷地創(chuàng)建和銷毀棧幀,Python 能夠跟蹤函數(shù)調(diào)用關(guān)系,保存和恢復(fù)局部變量的值,實(shí)現(xiàn)函數(shù)的嵌套和遞歸執(zhí)行。同時(shí),棧幀還可以用于實(shí)現(xiàn)異常處理、調(diào)試信息的收集和優(yōu)化技術(shù)等。

需要注意的是,棧幀是有限制的,Python 解釋器會(huì)對棧幀的數(shù)量和大小進(jìn)行限制,以防止棧溢出和資源耗盡的情況發(fā)生。在編寫 Python 程序時(shí),合理使用函數(shù)調(diào)用和棧幀可以幫助提高程序的性能和可維護(hù)性。

棧幀數(shù)據(jù)結(jié)構(gòu)

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;      /* previous frame, or NULL */
    PyCodeObject *f_code;       /* code segment */
    PyObject *f_builtins;       /* builtin symbol table (PyDictObject) */
    PyObject *f_globals;        /* global symbol table (PyDictObject) */
    PyObject *f_locals;         /* local symbol table (any mapping) */
    PyObject **f_valuestack;    /* points after the last local */
    /* Next free slot in f_valuestack.  Frame creation sets to f_valuestack.
       Frame evaluation usually NULLs it, but a frame that yields sets it
       to the current stack top. */
    PyObject **f_stacktop;
    PyObject *f_trace;          /* Trace function */

    /* In a generator, we need to be able to swap between the exception
       state inside the generator and the exception state of the calling
       frame (which shouldn't be impacted when the generator "yields"
       from an except handler).
       These three fields exist exactly for that, and are unused for
       non-generator frames. See the save_exc_state and swap_exc_state
       functions in ceval.c for details of their use. */
    PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
    /* Borrowed reference to a generator, or NULL */
    PyObject *f_gen;

    int f_lasti;                /* Last instruction if called */
    /* Call PyFrame_GetLineNumber() instead of reading this field
       directly.  As of 2.3 f_lineno is only valid when tracing is
       active (i.e. when f_trace is set).  At other times we use
       PyCode_Addr2Line to calculate the line from the current
       bytecode index. */
    int f_lineno;               /* Current line number */
    int f_iblock;               /* index in f_blockstack */
    char f_executing;           /* whether the frame is still executing */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
} PyFrameObject;

內(nèi)存申請和棧幀的內(nèi)存布局

在 cpython 當(dāng)中,當(dāng)我們需要申請一個(gè) frame object 對象的時(shí)候,首先需要申請內(nèi)存空間,但是在申請內(nèi)存空間的時(shí)候并不是單單申請一個(gè) frameobject 大小的內(nèi)存,而是會(huì)申請額外的內(nèi)存空間,大致布局如下所示。

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

  • f_localsplus,這是一個(gè)數(shù)組用戶保存函數(shù)執(zhí)行的 local 變量,這樣可以直接通過下標(biāo)得到對應(yīng)的變量的值。
  • ncells 和 nfrees,這個(gè)變量和我們前面在分析 code object 的函數(shù)閉包相關(guān),ncells 和 ncells 分別表示 cellvars 和 freevars 中變量的個(gè)數(shù)。
  • stack,這個(gè)變量就是函數(shù)執(zhí)行的時(shí)候函數(shù)的棧幀,這個(gè)大小在編譯期間就可以確定因此可以直接確定??臻g的大小。

下面是在申請 frame object 的核心代碼:

    Py_ssize_t extras, ncells, nfrees;
    ncells = PyTuple_GET_SIZE(code->co_cellvars); // 得到 co_cellvars 當(dāng)中元素的個(gè)數(shù) 沒有的話則是 0
    nfrees = PyTuple_GET_SIZE(code->co_freevars); // 得到 co_freevars 當(dāng)中元素的個(gè)數(shù) 沒有的話則是 0
    // extras 就是表示除了申請 frame object 自己的內(nèi)存之后還需要額外申請多少個(gè) 指針對象
    // 確切的帶來說是用于保存 PyObject 的指針
    extras = code->co_stacksize + code->co_nlocals + ncells +
        nfrees;
    if (free_list == NULL) {
        f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type,
        extras);
        if (f == NULL) {
            Py_DECREF(builtins);
            return NULL;
        }
    }
    // 這個(gè)就是函數(shù)的 code object 對象 將其保存到棧幀當(dāng)中 f 就是棧幀對象
    f->f_code = code;
    extras = code->co_nlocals + ncells + nfrees;
    // 這個(gè)就是棧頂?shù)奈恢?注意這里加上的 extras 并不包含棧的大小
    f->f_valuestack = f->f_localsplus + extras;
    // 對額外申請的內(nèi)存空間盡心初始化操作
    for (i=0; i<extras; i++)
        f->f_localsplus[i] = NULL;
    f->f_locals = NULL;
    f->f_trace = NULL;
    f->f_exc_type = f->f_exc_value = f->f_exc_traceback = NULL;

    f->f_stacktop = f->f_valuestack; // 將棧頂?shù)闹羔樦赶驐5钠鹗嘉恢?    f->f_builtins = builtins;
    Py_XINCREF(back);
    f->f_back = back;
    Py_INCREF(code);
    Py_INCREF(globals);
    f->f_globals = globals;
    /* Most functions have CO_NEWLOCALS and CO_OPTIMIZED set. */
    if ((code->co_flags & (CO_NEWLOCALS | CO_OPTIMIZED)) ==
        (CO_NEWLOCALS | CO_OPTIMIZED))
        ; /* f_locals = NULL; will be set by PyFrame_FastToLocals() */
    else if (code->co_flags & CO_NEWLOCALS) {
        locals = PyDict_New();
        if (locals == NULL) {
            Py_DECREF(f);
            return NULL;
        }
        f->f_locals = locals;
    }
    else {
        if (locals == NULL)
            locals = globals;
        Py_INCREF(locals);
        f->f_locals = locals;
    }

    f->f_lasti = -1;
    f->f_lineno = code->co_firstlineno;
    f->f_iblock = 0;
    f->f_executing = 0;
    f->f_gen = NULL;

現(xiàn)在我們對 frame object 對象當(dāng)中的各個(gè)字段進(jìn)行分析,說明他們的作用:

  • PyObject_VAR_HEAD:表示對象的頭部信息,包括引用計(jì)數(shù)和類型信息。
  • f_back:前一個(gè)棧幀對象的指針,或者為NULL。
  • f_code:指向 PyCodeObject 對象的指針,表示當(dāng)前幀執(zhí)行的代碼段。
  • f_builtins:指向 PyDictObject 對象的指針,表示當(dāng)前幀的內(nèi)置符號表,字典對象,鍵是字符串,值是對應(yīng)的 python 對象。
  • f_globals:指向 PyDictObject 對象的指針,表示當(dāng)前幀的全局符號表。
  • f_locals:指向任意映射對象的指針,表示當(dāng)前幀的局部符號表。
  • f_valuestack:指向當(dāng)前幀的值棧底部的指針。
  • f_stacktop:指向當(dāng)前幀的值棧頂部的指針。
  • f_trace:指向跟蹤函數(shù)對象的指針,用于調(diào)試和追蹤代碼執(zhí)行過程,這個(gè)字段我們在后面的文章當(dāng)中再進(jìn)行分析。
  • f_exc_type、f_exc_value、f_exc_traceback:這個(gè)字段和異常相關(guān),在函數(shù)執(zhí)行的時(shí)候可能會(huì)產(chǎn)生錯(cuò)誤異常,這個(gè)就是用于處理異常相關(guān)的字段。
  • f_gen:指向當(dāng)前生成器對象的指針,如果當(dāng)前幀不是生成器,則為NULL。
  • f_lasti:上一條指令在字節(jié)碼當(dāng)中的下標(biāo)。
  • f_lineno:當(dāng)前執(zhí)行的代碼行號。
  • f_iblock:當(dāng)前執(zhí)行的代碼塊在f_blockstack中的索引,這個(gè)字段也主要和異常的處理有關(guān)系。
  • f_executing:表示當(dāng)前幀是否仍在執(zhí)行。
  • f_blockstack:用于try和loop代碼塊的堆棧,最多可以嵌套 CO_MAXBLOCKS 層。
  • f_localsplus:局部變量和值棧的組合,是一個(gè)動(dòng)態(tài)大小的數(shù)組。

如果我們在一個(gè)函數(shù)當(dāng)中調(diào)用另外一個(gè)函數(shù),這個(gè)函數(shù)再調(diào)用其他函數(shù)就會(huì)形成函數(shù)的調(diào)用鏈,就會(huì)形成下圖所示的鏈?zhǔn)浇Y(jié)構(gòu)。

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

例子分析

我們現(xiàn)在來模擬一下下面的函數(shù)的執(zhí)行過程。

import dis


def foo():
    a = 1
    b = 2
    return a + b


if __name__ == '__main__':
    dis.dis(foo)
    print(foo.__code__.co_stacksize)
    foo()

上面的 foo 函數(shù)的字節(jié)碼如下所示:

  6           0 LOAD_CONST               1 (1)
              2 STORE_FAST               0 (a)

  7           4 LOAD_CONST               2 (2)
              6 STORE_FAST               1 (b)

  8           8 LOAD_FAST                0 (a)
             10 LOAD_FAST                1 (b)
             12 BINARY_ADD
             14 RETURN_VALUE

函數(shù) foo 的 stacksize 等于 2 。

初始時(shí) frameobject 的布局如下所示:

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

現(xiàn)在執(zhí)行第一條指令 LOAD_CONST 此時(shí)的 f_lasti 等于 -1,執(zhí)行完這條字節(jié)碼之后棧幀情況如下:

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

在執(zhí)行完這條字節(jié)碼之后 f_lasti 的值變成 0。字節(jié)碼 LOAD_CONST 對應(yīng)的 c 源代碼如下所示:

TARGET(LOAD_CONST) {
    PyObject *value = GETITEM(consts, oparg); // 從常量表當(dāng)中取出下標(biāo)為 oparg 的對象
    Py_INCREF(value);
    PUSH(value);
    FAST_DISPATCH();
}

首先是從 consts 將對應(yīng)的常量拿出來,然后壓入棧空間當(dāng)中。

再執(zhí)行 STORE_FAST 指令,這個(gè)指令就是將棧頂?shù)脑貜棾鋈缓蟊4娴角懊嫣岬降?f_localsplus 數(shù)組當(dāng)中去,那么現(xiàn)在??臻g是空的。STORE_FAST 對應(yīng)的 c 源代碼如下:

TARGET(STORE_FAST) {
    PyObject *value = POP(); // 將棧頂元素彈出
    SETLOCAL(oparg, value);  // 保存到 f_localsplus 數(shù)組當(dāng)中去
    FAST_DISPATCH();
}

執(zhí)行完這條指令之后 f_lasti 的值變成 2 。

接下來的兩條指令和上面的一樣,就不做分析了,在執(zhí)行完兩條指令,f_lasti 變成 6 。

接下來兩條指令分別將 a b 加載進(jìn)入??臻g單中現(xiàn)在棧空間布局如下所示:

深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀

然后執(zhí)行 BINARY_ADD 指令 彈出??臻g的兩個(gè)元素并且把他們進(jìn)行相加操作,最后將得到的結(jié)果再壓回??臻g當(dāng)中。

TARGET(BINARY_ADD) {
    PyObject *right = POP();
    PyObject *left = TOP();
    PyObject *sum;
    if (PyUnicode_CheckExact(left) &&
             PyUnicode_CheckExact(right)) {
        sum = unicode_concatenate(left, right, f, next_instr);
        /* unicode_concatenate consumed the ref to left */
    }
    else {
        sum = PyNumber_Add(left, right);
        Py_DECREF(left);
    }
    Py_DECREF(right);
    SET_TOP(sum); // 將結(jié)果壓入棧中
    if (sum == NULL)
        goto error;
    DISPATCH();
}

最后執(zhí)行 RETURN_VALUE 指令將??臻g結(jié)果返回。

總結(jié)

在本篇文章當(dāng)中主要介紹了 cpython 當(dāng)中的函數(shù)執(zhí)行的時(shí)候的棧幀結(jié)構(gòu),這里面包含的程序執(zhí)行時(shí)候所需要的一些必要的變量,比如說全局變量,python 內(nèi)置的一些對象等等,同時(shí)需要注意的是 python 在查詢對象的時(shí)候如果本地 f_locals 沒有找到就會(huì)去全局 f_globals 找,如果還沒有找到就會(huì)去 f_builtins 里面的找,當(dāng)一個(gè)程序返回的時(shí)候就會(huì)找到 f_back 他上一個(gè)執(zhí)行的棧幀,將其設(shè)置成當(dāng)前線程正在使用的棧幀,這就完成了函數(shù)的調(diào)用返回,關(guān)于這個(gè)棧幀還有一些其他的字段我們沒有談到在后續(xù)的文章當(dāng)中將繼續(xù)深入其中一些字段。


本篇文章是深入理解 python 虛擬機(jī)系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython

更多精彩內(nèi)容合集可訪問項(xiàng)目:https://github.com/Chang-LeHung/CSCore

關(guān)注公眾號:一無是處的研究僧,了解更多計(jì)算機(jī)(Java、Python、計(jì)算機(jī)系統(tǒng)基礎(chǔ)、算法與數(shù)據(jù)結(jié)構(gòu))知識。文章來源地址http://www.zghlxwxcb.cn/news/detail-424209.html

到了這里,關(guān)于深入理解python虛擬機(jī):程序執(zhí)行的載體——棧幀的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入理解 python 虛擬機(jī):花里胡哨的魔術(shù)方法

    在本篇文章當(dāng)中主要給大家介紹在 cpython 當(dāng)中一些比較花里胡哨的魔術(shù)方法,以幫助我們自己實(shí)現(xiàn)比較花哨的功能,當(dāng)然這其中也包含一些也非常實(shí)用的魔術(shù)方法。 在 Python 中, __hash__() 方法是一種特殊方法(也稱為魔術(shù)方法或雙下劃線方法),用于返回對象的哈希值。哈希

    2024年02月06日
    瀏覽(20)
  • 深入理解python虛擬機(jī):調(diào)試器實(shí)現(xiàn)原理與源碼分析

    深入理解python虛擬機(jī):調(diào)試器實(shí)現(xiàn)原理與源碼分析

    調(diào)試器是一個(gè)編程語言非常重要的部分,調(diào)試器是一種用于診斷和修復(fù)代碼錯(cuò)誤(或稱為 bug)的工具,它允許開發(fā)者在程序執(zhí)行時(shí)逐步查看和分析代碼的狀態(tài)和行為,它可以幫助開發(fā)者診斷和修復(fù)代碼錯(cuò)誤,理解程序的行為,優(yōu)化性能。無論在哪種編程語言中,調(diào)試器都是一

    2023年04月26日
    瀏覽(38)
  • 深入理解JVM虛擬機(jī)第十三篇:詳解JVM中的程序計(jì)數(shù)器

    深入理解JVM虛擬機(jī)第十三篇:詳解JVM中的程序計(jì)數(shù)器

    ???? 學(xué)習(xí)交流群: ??1:這是孫哥suns給大家的福利! ??2:我們免費(fèi)分享Netty、Dubbo、k8s、Mybatis、Spring...應(yīng)用和源碼級別的視頻資料 ????3:QQ群:583783824 ? ???? ?工作微信:BigTreeJava 拉你進(jìn)微信群,免費(fèi)領(lǐng)?。?????4:本文章內(nèi)容出自上述:Spring應(yīng)用課程!????

    2024年02月08日
    瀏覽(23)
  • 深入理解 python 虛擬機(jī):破解核心魔法——反序列化 pyc 文件

    深入理解 python 虛擬機(jī):破解核心魔法——反序列化 pyc 文件

    在前面的文章當(dāng)中我們詳細(xì)的對于 pyc 文件的結(jié)構(gòu)進(jìn)行了分析,pyc 文件主要有下面的四個(gè)部分組成:魔術(shù)、 Bite Filed 、修改日期和 Code Object 組成。在前面的文章當(dāng)中我們已經(jīng)對前面三個(gè)部分進(jìn)行了字節(jié)角度的分析,直接從 pyc 文件當(dāng)中讀取對應(yīng)的數(shù)據(jù)并且打印出來了。而在本

    2024年02月05日
    瀏覽(23)
  • 深入理解 python 虛擬機(jī):字節(jié)碼教程(2)——控制流是如何實(shí)現(xiàn)的?

    深入理解 python 虛擬機(jī):字節(jié)碼教程(2)——控制流是如何實(shí)現(xiàn)的?

    在本篇文章當(dāng)中主要給大家分析 python 當(dāng)中與控制流有關(guān)的字節(jié)碼,通過對這部分字節(jié)碼的了解,我們可以更加深入了解 python 字節(jié)碼的執(zhí)行過程和控制流實(shí)現(xiàn)原理。 控制流這部分代碼主要涉及下面幾條字節(jié)碼指令,下面的所有字節(jié)碼指令都會(huì)有一個(gè)參數(shù): JUMP_FORWARD ,指令完

    2023年04月10日
    瀏覽(18)
  • 深入理解 python 虛擬機(jī):字節(jié)碼教程(1)——原來裝飾器是這樣實(shí)現(xiàn)的

    深入理解 python 虛擬機(jī):字節(jié)碼教程(1)——原來裝飾器是這樣實(shí)現(xiàn)的

    在本篇文章當(dāng)中主要給大家介紹在 cpython 當(dāng)中一些比較常見的字節(jié)碼,從根本上理解 python 程序的執(zhí)行。在本文當(dāng)中主要介紹一些 python 基本操作的字節(jié)碼,并且將從字節(jié)碼的角度分析函數(shù)裝飾器的原理! 這個(gè)指令用于將一個(gè)常量加載到棧中。常量可以是數(shù)字、字符串、元組

    2023年04月09日
    瀏覽(18)
  • 深入理解 python 虛擬機(jī):描述器的王炸應(yīng)用-property、staticmethod 和 classmehtod

    在本篇文章當(dāng)中主要給大家介紹描述器在 python 語言當(dāng)中有哪些應(yīng)用,主要介紹如何使用 python 語言實(shí)現(xiàn) python 內(nèi)置的 proterty 、staticmethod 和 class method 。 當(dāng)你在編寫Python代碼時(shí),你可能會(huì)遇到一些需要通過方法來訪問或設(shè)置的屬性。Python中的 property 裝飾器提供了一種優(yōu)雅的方

    2024年02月03日
    瀏覽(21)
  • 深入理解高并發(fā)編程 - 線程的執(zhí)行順序

    在Java中,線程的執(zhí)行順序是由操作系統(tǒng)的調(diào)度機(jī)制決定的,具體順序是不確定的,取決于多個(gè)因素,如操作系統(tǒng)的調(diào)度策略、線程的優(yōu)先級、線程的狀態(tài)轉(zhuǎn)換等。因此,不能對線程的執(zhí)行順序做出可靠的假設(shè)。 以下是一個(gè)簡單的Java代碼示例,演示了多個(gè)線程的執(zhí)行順序是不

    2024年02月14日
    瀏覽(24)
  • 深入理解Linux虛擬內(nèi)存管理

    深入理解Linux虛擬內(nèi)存管理

    Linux 內(nèi)核設(shè)計(jì)與實(shí)現(xiàn) 深入理解 Linux 內(nèi)核 Linux 設(shè)備驅(qū)動(dòng)程序 Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解 深入理解Linux虛擬內(nèi)存管理(一) 深入理解Linux虛擬內(nèi)存管理(二) 深入理解Linux虛擬內(nèi)存管理(三) 深入理解Linux虛擬內(nèi)存管理(四) 深入理解Linux虛擬內(nèi)存管理(五) 深入理解Linux虛擬內(nèi)存

    2024年02月06日
    瀏覽(18)
  • 深入理解Java虛擬機(jī)(讀書筆記)

    深入理解Java虛擬機(jī)(讀書筆記)

    JCP:Java Community Process(Java社區(qū)) JSR:Java Specification Requests(Java規(guī)范提案) JEP:JDK Enhancement Proposals(Oracle Java版本管理) JMM:Java Memory Model(Java內(nèi)存模型) OSR:On-Stack Replacement(棧上替換) TCK:Technology Compatibility Kit(技術(shù)兼容性測試工具) AOT:Ahead of Time Compilation(提前編

    2024年02月08日
    瀏覽(16)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包