【Python】虛擬機執(zhí)行框架

0x01 Python虛擬機中的執(zhí)行環(huán)境

Python虛擬機在執(zhí)行Python代碼時,是模擬操作系統(tǒng)執(zhí)行可執(zhí)行文件的過程。

ESPExtended stack pointer)為棧指針席噩,用于指向棧的棧頂(下一個壓入棧的活動記錄的頂部)月匣,而EBPextended base pointer)為幀指針承边,指向當前活動記錄的底部

對于一個函數(shù)而言桌肴,其所有對局部變量的操作都在自己的棧幀中完成,而函數(shù)之間的調(diào)用則通過創(chuàng)建新的棧幀完成色鸳。

操作系統(tǒng)運行時棧的某一時刻

Python源代碼編譯完成后社痛,所有的字節(jié)碼指令和靜態(tài)信息都在PyCodeObject對象中,但是只有這些還是不夠的命雀,還需要執(zhí)行環(huán)境蒜哀。

執(zhí)行環(huán)境(PyFrameObject)

Python執(zhí)行test.py的第一條表達式時,會創(chuàng)建一個執(zhí)行環(huán)境APyFrameObject *A)吏砂,所有的字節(jié)碼都在這個執(zhí)行環(huán)境A中執(zhí)行撵儿,Python可以從這個執(zhí)行環(huán)境中獲取變量的值,也可以根據(jù)字節(jié)碼指令修改執(zhí)行環(huán)境中變量的值狐血,以影響后續(xù)的字節(jié)碼指令淀歇;

這樣的過程會一直持續(xù)下去,直到發(fā)生函數(shù)的調(diào)用匈织,執(zhí)行函數(shù)調(diào)用的字節(jié)碼時浪默,會在當前執(zhí)行環(huán)境A之外創(chuàng)建一個新的執(zhí)行環(huán)境BPyFrameObject *B)。

這塊的執(zhí)行環(huán)境對應運行時棧中的棧幀缀匕。

typedef struct _frame {
    PyObject_VAR_HEAD
    struct _frame *f_back;  /* 執(zhí)行環(huán)境鏈上的前一個frame previous frame, or NULL */
    PyCodeObject *f_code;   /* PyCodeObject對象 code segment */
    PyObject *f_builtins;   /* builtin名字空間 builtin symbol table (PyDictObject) */
    PyObject *f_globals;    /* global名字空間 global symbol table (PyDictObject) */
    PyObject *f_locals;     /* local名字空間 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 */

    /* If an exception is raised in this frame, the next three are used to
     * record the exception info (if any) originally in the thread state.  See
     * comments before set_exc_info() -- it's not obvious.
     * Invariant:  if _type is NULL, then so are _value and _traceback.
     * Desired invariant:  all three are NULL, or all three are non-NULL.  That
     * one isn't currently true, but "should be".
     */
    PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;

    PyThreadState *f_tstate;
    int f_lasti;        /* 上一條字節(jié)碼指令在f_code中的偏移位置 Last instruction if called */
    /* As of 2.3 f_lineno is only valid when tracing is active (i.e. when
       f_trace is set) -- at other times use PyCode_Addr2Line instead. */
    int f_lineno;       /* 當前字節(jié)碼對應的源代碼行 Current line number */
    int f_iblock;       /* index in f_blockstack */
    PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
    // 動態(tài)內(nèi)存纳决,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需的空間
    PyObject *f_localsplus[1];  /* locals+stack, dynamically sized */
} PyFrameObject;
  • f_back指向上一個棧幀,用這個域來模擬操作系統(tǒng)中棧幀的關系(操作系統(tǒng)使用ebpesp來維護棧幀關系)
  • f_code中存放的是待執(zhí)行的PyCodeObject對象
  • f_builtins乡小、f_globals阔加、f_locals3個獨立的名字空間(這里可以明確執(zhí)行環(huán)境和名字空間的關系
  • PyObject_VAR_HEAD表示PyFrameObject是一個變長的對象,類似于PyStringObject一樣满钟,每個新對象的大小可能不一樣胜榔。那變長的內(nèi)存是用來干啥的呢?
    • 每一個PyFrameObject對象都維護了一個PyCodeObject對象
    • 在將.py代碼編譯成PyCodeObject對象的時候湃番,會計算這段Code Block執(zhí)行過程中所需的椮仓空間大小,存儲在PyCodeObject.co_stacksize域牵辣,不同的Code Block所需的椝ぱⅲ空間不同,這個就是變長的內(nèi)存的用處
  • Python在執(zhí)行計算的時候也需要一些內(nèi)存空間(存儲臨時變量等內(nèi)容)纬向,我們將其稱為“運行時椩褡牵”

PyFrameObject中的動態(tài)內(nèi)存空間

// frameobject.c 有刪減
PyFrameObject *
PyFrame_New(PyThreadState *tstate, PyCodeObject *code, PyObject *globals,
            PyObject *locals)
{
    // 從PyThreadState對象中獲取得到當前線程的執(zhí)行環(huán)境
    PyFrameObject *back = tstate->frame;
    PyFrameObject *f;
    PyObject *builtins;
    Py_ssize_t i;
    Py_ssize_t extras, ncells, nfrees;
    ncells = PyTuple_GET_SIZE(code->co_cellvars);
    nfrees = PyTuple_GET_SIZE(code->co_freevars);
    // 4部分構成了PyFrameObject維護的動態(tài)內(nèi)存區(qū)
    extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
    // 創(chuàng)建新的執(zhí)行環(huán)境
    f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
    // 計算初始化時“運行時棧”的棧頂
    extras = code->co_nlocals + ncells + nfrees;
    // f_valuestack 維護“運行時椨馓酰”的棧底琢岩,f_stacktop 維護“運行時棧”的棧頂
    f->f_valuestack = f->f_localsplus + extras;
    f->f_stacktop = f->f_valuestack;
    // 鏈接當前執(zhí)行環(huán)境
    f->f_back = back;
    f->f_tstate = tstate;
    return f;
}
新創(chuàng)建的PyFrameObject對象
  • PyFrameObject.f_localsplus域包含了PyCodeObject對象中存儲的局部變量(co_nlocals)师脂、co_freevars担孔、co_cellvars和運行時棧。
  • f_valuestack指向棧底吃警,f_stacktop指向棧頂糕篇。

0x02 名字、作用域和名字空間

名字:就是一個符號酌心,用于代表某些事物的一個有助于記憶的字符序列拌消。名字最終的作用并不在于名字本身,而在于名字所代表的事物安券。

作用域:Python是具有靜態(tài)作用域(也稱詞法作用域)的墩崩,即作用域由源程序的文本決定的,一個Code Block就是一個作用域侯勉,在寫Python代碼的時候鹦筹,作用域就已經(jīng)確定了

名字空間:名字空間是與作用域?qū)膭討B(tài)的東西,上面提到的f_builtins址貌、f_globals铐拐、f_locals 3個獨立的名字空間

約束與名字空間

賦值語句(具有賦值行為的語句,import xxx练对、class A(object):都算)是一類特殊的語句余舶,因為它會影響名字空間。

賦值語句被執(zhí)行了以后锹淌,會得到(name匿值,obj)這樣的關聯(lián)關系,稱之為約束赂摆。賦值語句就是建立約束的地方挟憔,約束的容身之處就是名字空間。

Python中名字空間用PyDictObject對象表示烟号,約束剛好與鍵值對對應起來绊谭。

一個對象的名字空間中所有的名字都稱為對象的屬性。這樣看汪拥,賦值語句也具有“設置對象屬性的行為”达传,那“訪問對象屬性”的動作稱為屬性引用,屬性引用就是使用另一個名字空間中的名字(eg:import A&print A.a

作用域與名字空間

一個module對應一個名字空間,module內(nèi)部可能有多個名字空間宪赶,每一個名字空間與一個作用域?qū)?/p>

一個約束起作用的那一段程序正文區(qū)域稱為這個約束的作用域宗弯。一個作用域則是指一段程序正文區(qū)域,一旦出了這個正文區(qū)域搂妻,這個約束就不起作用了蒙保。

位于一個作用域中的代碼可以直接訪問作用域中出現(xiàn)的名字,稱為“直接訪問”(直接print a不需要print A.a)欲主。就是指不用加上屬性引用方式的訪問修飾符“.”邓厕。訪問名字這樣的行為被稱為“名字引用”。

Python的名字空間的行為被它所支持的嵌套作用域影響扁瓢,產(chǎn)生了最內(nèi)嵌套作用域規(guī)則LEGB):由一個賦值語句引進的名字在這個賦值語句所在的作用域里是可見(起作用)的详恼,而且在其內(nèi)部嵌套的每一個作用域里也是可見的,除非嵌套的作用域內(nèi)引几,被引進了同樣名字所遮蔽昧互。

LGB

Python中,一個module對應的源文件定義了一個作用域她紫,稱為global作用域(對應global名字空間)硅堆;

一個函數(shù)定義了一個local作用域(對應local名字空間);

Python自身還定義了一個最頂層作用域贿讹,builtin作用域(對應builtin名字空間)渐逃;

Python 2.2之前這3個作用域就已經(jīng)存在,被稱為LGB規(guī)則:名字引用動作沿著local作用域民褂、global作用域茄菊、builtin作用域的順序查找名字的對應約束。

LEGB

Python 2.2開始赊堪,Python引入嵌套函數(shù)面殖,這時候又加了一層enclosing作用域,稱為LEGB哭廉。

嵌套函數(shù)會將在直接外圍作用域中使用到的約束脊僚,與嵌套函數(shù)捆綁在一起,捆綁起來的整體被稱為“閉包”遵绰。

global表達式

當一個作用域中出現(xiàn)global語句時辽幌,就意味著我們強制命令Python對某個名字的引用只參考global名字空間,而不用去管LEGB規(guī)則椿访。

屬性引用與名字引用

屬性引用實質(zhì)上也是一種名字引用乌企,其本質(zhì)就是到名字空間中去查找一個名字所引用的對象。但是屬性引用可以視為一種特殊的名字引用成玫,它不受LEGB規(guī)則制約加酵。

屬性引用沒有嵌套作用域拳喻,在名字空間中查找名字時,有就有猪腕,沒有就沒有冗澈,沒有更多的規(guī)則限制。

名字引用遵循的LEGB規(guī)則不會越過module的邊界码撰。

0x03 Python虛擬機的運行框架(偽CPU)

Python啟動后渗柿,首先會進行Python運行時環(huán)境的初始化个盆。這里的運行時環(huán)境和上面提到的執(zhí)行環(huán)境是不同的概念脖岛。運行時環(huán)境是一個全局的概念,而執(zhí)行環(huán)境實際上就是一個棧幀(PYFrameObject)颊亮,是一個和某一個Code Block對應的概念柴梆。

初始化完成以后,就開始運行程序终惑,入口就是PyEval_EvalFramEx()绍在,這個函數(shù)就是Python虛擬機的具體實現(xiàn)

PyEval_EvalFramEx()函數(shù)代碼有2000多行雹有,就不列出所有代碼了偿渡。

  • 首先會初始化一些變量;然后初始化了堆棧的棧頂指針霸奕,使其指向f->f_stacktop
  • PyCodeObject對象的co_code域中保存著字節(jié)碼指令和字節(jié)碼指令的參數(shù)溜宽,其實它就是C中的普通字符數(shù)組,使用3個變量來遍歷整個字節(jié)碼字符數(shù)組:first_instr永遠指向字節(jié)碼指令序列的開始位置质帅,next_instr永遠指向下一條待執(zhí)行的字節(jié)碼指令位置适揉,f_lasti指向上一條已經(jīng)執(zhí)行過的字節(jié)碼指令位置。
  • 執(zhí)行字節(jié)碼指令的動作是如何實現(xiàn)的呢煤惩?
    • Python虛擬機執(zhí)行字節(jié)碼指令的整體框架:其實就是一個for循環(huán)加上一個巨大的switch/case結(jié)構嫉嘀。
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
  ......
  // 獲取當前活動線程的線程狀態(tài)對象(PyThreadState)
  PyThreadState *tstate = PyThreadState_GET();
  // 設置線程狀態(tài)對象中的frame
  tstate->frame = f;
  co = f->f_code;
  names = co->co_names;
  consts = co->co_consts;

  why = WHY_NOT;
  ......
  for (;;) {
    fast_next_opcode:
        f->f_lasti = INSTR_OFFSET();
        // 獲取字節(jié)碼指令
        opcode = NEXTOP();
        oparg = 0; 
        // 如果指令有參數(shù),獲取參數(shù)
        if (HAS_ARG(opcode))
            oparg = NEXTARG();
    dispatch_opcode:
      ......
  }
}

在這個執(zhí)行框架中魄揉,對字節(jié)碼的一步一步的遍歷是通過下面幾個宏來實現(xiàn)的:

// ceval.c
/* Code access macros */
#define INSTR_OFFSET()  ((int)(next_instr - first_instr))
#define NEXTOP()        (*next_instr++)
#define NEXTARG()       (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2])
#define PEEKARG()       ((next_instr[2]<<8) + next_instr[1])
#define JUMPTO(x)       (next_instr = first_instr + (x))
#define JUMPBY(x)       (next_instr += (x))
  • Python的字節(jié)碼有的是帶參數(shù)的剪侮,有的是沒有參數(shù)的(通過HAS_ARG宏來判斷),所以next_instr的位移可能是不同的洛退,但是無論如何瓣俯,next_instr總是指向Python的下一條要執(zhí)行的字節(jié)碼。
  • 然后Python在獲得了一條字節(jié)碼指令和其所需的指令參數(shù)后不狮,會對字節(jié)碼利用switch進行判斷降铸,根據(jù)不同的指令來執(zhí)行不同的case語句,case語句就是Python對字節(jié)碼指令的具體實現(xiàn)摇零。
  • 在成功執(zhí)行完一條字節(jié)碼指令后推掸,Python的執(zhí)行流程會跳轉(zhuǎn)到fast_next_opcode或者for循環(huán)處,不管如何,接下來的動作都是獲取下一條字節(jié)碼指令和指令參數(shù)谅畅,然后執(zhí)行指令對應的case語句登渣。
  • 如此一條一條的遍歷co_code中包含的所有字節(jié)碼指令,最終完成了對Python程序的執(zhí)行毡泻。

why變量:它指示了在退出這個巨大的for循環(huán)時Python執(zhí)行引擎的狀態(tài)胜茧。
在執(zhí)行字節(jié)碼過程中可能會報錯或出現(xiàn)異常(exception),在退出的時候我們需要知道原因仇味,why就扮演者這個角色呻顽。

/* Status code for main loop (reason for stack unwind) */
enum why_code {
        WHY_NOT =       0x0001, /* No error */
        WHY_EXCEPTION = 0x0002, /* Exception occurred */
        WHY_RERAISE =   0x0004, /* Exception re-raised by 'finally' */
        WHY_RETURN =    0x0008, /* 'return' statement */
        WHY_BREAK =     0x0010, /* 'break' statement */
        WHY_CONTINUE =  0x0020, /* 'continue' statement */
        WHY_YIELD =     0x0040  /* 'yield' operator */
};

0x04 Python運行時環(huán)境初探

進程(process)不是與機器指令序列相對應的活動對象,這個與可執(zhí)行文件中機器指令序列對應的活動對象是線程(Thread)丹墨,而進程是線程的活動對象

講人話就是說廊遍,在CPU上執(zhí)行任務的不是進程而是線程。

前面已經(jīng)講了執(zhí)行框架執(zhí)行環(huán)境贩挣,現(xiàn)在就來了解一下運行時環(huán)境喉前。

Python在初始化時會創(chuàng)建一個主線程,所以其運行時環(huán)境中存在一個主線程王财。Python中的一個線程就是操作系統(tǒng)上的一個原生線程卵迂。

前面講了Python虛擬機的運行框架(for&switch/case),這個運行框架就是對CPU的抽象(就把這個執(zhí)行框架認為是軟CPU就行)绒净。Python中所有線程都使用這個軟CPU來完成計算工作见咒。

真實機器上的任務切換機制對應到Python中,就是使不同的線程輪流使用虛擬機的機制疯溺。

CPU切換任務時需要保存線程上下文環(huán)境论颅,對于Python來說,切換線程之前也需要保存當前線程信息囱嫩。Python中使用PyThreadState對象來保存線程狀態(tài)信息恃疯。一個線程擁有一個PyThreadState對象。

Python對于進程的抽象是由PyInterpreterState對象來實現(xiàn)的墨闲。一個進程中可以有多個線程今妄,線程的同步通過全局解釋器鎖GILGlobal Interpreter Lock)來實現(xiàn)。

typedef struct _is {

    struct _is *next;
    struct _ts *tstate_head;    // 模擬進程環(huán)境中的線程集合
    PyObject *modules;
    PyObject *sysdict;
    PyObject *builtins;
    PyObject *modules_reloading;

    PyObject *codec_search_path;
    PyObject *codec_search_cache;
    PyObject *codec_error_registry;
} PyInterpreterState;

typedef struct _ts {
    struct _ts *next;
    PyInterpreterState *interp;

    struct _frame *frame;    // 模擬線程中的函數(shù)調(diào)用堆棧
    int recursion_depth;
    int tracing;
    int use_tracing;

    Py_tracefunc c_profilefunc;
    Py_tracefunc c_tracefunc;
    PyObject *c_profileobj;
    PyObject *c_traceobj;

    PyObject *curexc_type;
    PyObject *curexc_value;
    PyObject *curexc_traceback;

    PyObject *exc_type;
    PyObject *exc_value;
    PyObject *exc_traceback;

    PyObject *dict;  /* Stores per-thread state */

    int tick_counter;

    int gilstate_counter;

    PyObject *async_exc; /* Asynchronous exception to raise */
    long thread_id; /* Thread id where this tstate was created */

    int trash_delete_nesting;
    PyObject *trash_delete_later;
} PyThreadState;
  • 在每個PyThreadState對象中鸳碧,會維護一個棧幀列表盾鳞,以與PyThreadState對象的線程中的函數(shù)調(diào)用機制對應。
  • PyEval_EvalFrameEx()函數(shù)中瞻离,會將當前線程狀態(tài)對象(PyThreadState)中的frame設置為當前的執(zhí)行難環(huán)境(frame)腾仅。
// ceval.c
PyObject *
PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
  ......
  // 獲取當前活動線程的線程狀態(tài)對象(PyThreadState)
  PyThreadState *tstate = PyThreadState_GET();
  // 設置線程狀態(tài)對象中的frame
  tstate->frame = f;
  co = f->f_code;
  names = co->co_names;
  consts = co->co_consts;

  why = WHY_NOT;
  ......
  for (;;) {
    fast_next_opcode:
        f->f_lasti = INSTR_OFFSET();
        // 獲取字節(jié)碼指令
        opcode = NEXTOP();
        oparg = 0; 
        // 如果指令有參數(shù),獲取參數(shù)
        if (HAS_ARG(opcode))
            oparg = NEXTARG();
    dispatch_opcode:
      ......
  }
}
  • 而在建立新的PyFrameObject對象時套利,則從當前活動線程的線程狀態(tài)對象PyThreadState中取出舊的frame推励,建立PyFrameObject鏈表鹤耍。
Python運行時環(huán)境

歡迎關注微信公眾號(coder0x00)或掃描下方二維碼關注,我們將持續(xù)搜尋程序員必備基礎技能包提供給大家验辞。


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末稿黄,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子跌造,更是在濱河造成了極大的恐慌杆怕,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壳贪,死亡現(xiàn)場離奇詭異陵珍,居然都是意外死亡,警方通過查閱死者的電腦和手機撑碴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門撑教,熙熙樓的掌柜王于貴愁眉苦臉地迎上來朝墩,“玉大人醉拓,你說我怎么就攤上這事∈账眨” “怎么了亿卤?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長鹿霸。 經(jīng)常有香客問我排吴,道長,這世上最難降的妖魔是什么懦鼠? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任钻哩,我火速辦了婚禮,結(jié)果婚禮上肛冶,老公的妹妹穿的比我還像新娘街氢。我一直安慰自己,他們只是感情好睦袖,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布珊肃。 她就那樣靜靜地躺著,像睡著了一般馅笙。 火紅的嫁衣襯著肌膚如雪伦乔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天董习,我揣著相機與錄音烈和,去河邊找鬼。 笑死皿淋,一個胖子當著我的面吹牛招刹,可吹牛的內(nèi)容都是我干的虱颗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼蔗喂,長吁一口氣:“原來是場噩夢啊……” “哼忘渔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缰儿,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤畦粮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后乖阵,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宣赔,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年瞪浸,在試婚紗的時候發(fā)現(xiàn)自己被綠了儒将。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡对蒲,死狀恐怖钩蚊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蹈矮,我是刑警寧澤砰逻,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站泛鸟,受9級特大地震影響蝠咆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜北滥,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一刚操、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧再芋,春花似錦菊霜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至联喘,卻和暖如春华蜒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背豁遭。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工叭喜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蓖谢。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓捂蕴,卻偏偏與公主長得像譬涡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子啥辨,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 原文地址:C語言函數(shù)調(diào)用棧(一)C語言函數(shù)調(diào)用棧(二) 0 引言 程序的執(zhí)行過程可看作連續(xù)的函數(shù)調(diào)用涡匀。當一個函數(shù)執(zhí)...
    小豬啊嗚閱讀 4,616評論 1 19
  • 一、溫故而知新 1. 內(nèi)存不夠怎么辦 內(nèi)存簡單分配策略的問題地址空間不隔離內(nèi)存使用效率低程序運行的地址不確定 關于...
    SeanCST閱讀 7,815評論 0 27
  • 在一個寂靜無人的夜晚 閑的無聊溉知,便進入QQ語聊大廳和陌生人聊天陨瘩。這里不需要問你的年齡、性別级乍、工作舌劳、是否單身,...
    小七姑娘678閱讀 371評論 0 1
  • 我向遠方呼喚 你沒有聽見 我不怪你 因為你離我實在太遠 我向大山呼喚 你沒聽見 我不怪你 因為你在山的那一邊 我向...
    港仔1961閱讀 485評論 0 1
  • 三十年后玫荣,外聘到廣東五邑大學甚淡,終于看到了類似的產(chǎn)品,鄰居章老師兩歲的小孫子穿了一雙響聲鞋捅厂,每走一步就發(fā)出悅耳聲響贯卦,...
    頌奇2018閱讀 215評論 0 1