亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

【PHP源碼學(xué)習(xí)】2019-03-18 復(fù)習(xí)前面的內(nèi)容

lindroid / 3347人閱讀

摘要:調(diào)用函數(shù)時,它將用戶釋放的內(nèi)存塊連接到空閑鏈上。這個聯(lián)合體共占用字節(jié)。是數(shù)字,且順序遞增位置固定,如訪問是的元素,即,就直接訪問數(shù)組的第個位置即可即,這樣就不需要前面的索引數(shù)組。

baiyan

全部視頻:https://segmentfault.com/a/11...

原視頻地址:http://replay.xesv5.com/ll/24...

本筆記中部分圖片截自視頻中的片段,圖片版權(quán)歸視頻原作者所有。

malloc函數(shù)深入

在PHP內(nèi)存管理1筆記中提到,malloc()函數(shù)會在分配的內(nèi)存空間前面額外分配32位,用來存儲分配的大小和幾個標(biāo)志位,如圖:

那么究竟是否是這樣的呢?我們寫一段測試代碼驗證一下:

#include 
int main() {
    void *ptr = malloc(8);
    return 1;
}

利用gdb調(diào)試這段代碼:

首先打印ptr的地址,為0x602010,利用x命令往后看20個內(nèi)存單元(1個內(nèi)存單元 = 4個字節(jié)),故一共展示了80個字節(jié),后面的x是以16進制打印內(nèi)容。

我們發(fā)現(xiàn)緊鄰0x602010地址的上面32位均是0,沒有任何內(nèi)容,不符合我們的預(yù)期。

上圖只是一個最簡單的思路,但絕大多數(shù)操作系統(tǒng)是按照如下的方式實現(xiàn)的:

操作系統(tǒng)中有一個記錄空閑內(nèi)存地址的鏈表。當(dāng)操作系統(tǒng)收到程序的申請時,就會遍歷該鏈表,然后就尋找第一個空間大于所申請空間的堆結(jié)點,然后就將該結(jié)點從空閑結(jié)點鏈表中刪除,并將該結(jié)點的空間分配給程序。malloc函數(shù)的實質(zhì)體現(xiàn)在,它有一個將可用的內(nèi)存塊連接為一個長長的列表的所謂空閑鏈表(Free List)。調(diào)用malloc函數(shù)時,它沿連接表尋找一個大到足以滿足用戶請求所需要的內(nèi)存塊(根據(jù)不同的算法而定(將最先找到的不小于申請的大小內(nèi)存塊分配給請求者,將最合適申請大小的空閑內(nèi)存分配給請求者,或者是分配最大的空閑塊內(nèi)存塊)。然后,將該內(nèi)存塊一分為二(一塊的大小與用戶請求的大小相等,另一塊的大小就是剩下的字節(jié))。接下來,將分配給用戶的那塊內(nèi)存?zhèn)鹘o用戶,并將剩下的那塊(如果有的話)返回到連接表上。調(diào)用free函數(shù)時,它將用戶釋放的內(nèi)存塊連接到空閑鏈上。到最后,空閑鏈會被切成很多的小內(nèi)存片段,如果這時用戶申請一個大的內(nèi)存片段,那么空閑鏈上可能沒有可以滿足用戶要求的片段了。于是,malloc函數(shù)請求延時,并開始在空閑鏈上翻箱倒柜地檢查各內(nèi)存片段,對它們進行整理,將相鄰的小空閑塊合并成較大的內(nèi)存塊。如果無法獲得符合要求的內(nèi)存塊,malloc函數(shù)會返回NULL指針,因此在調(diào)用malloc動態(tài)申請內(nèi)存塊時,一定要進行返回值的判斷。
結(jié)構(gòu)體與聯(lián)合體 結(jié)構(gòu)體

在PHP內(nèi)存管理2筆記中,我們談到了一種特殊情況:

在b是char類型的時候,a和b的內(nèi)存地址是緊鄰的;如果b是int類型的話,就會出現(xiàn)如圖所示的情況。我們可以這樣記憶:不看b之后的字段,a和b之前也是按照它們的最小公倍數(shù)對齊的(如果b是int類型,a和b的最小公倍數(shù)是4,按4對齊;如果b是char類型,最小公倍數(shù)為1,按1對齊,就會出現(xiàn)a和b緊鄰的情況)

如果不想對齊,有如下解決方案:

編譯的時候不加優(yōu)化參數(shù)

代碼層面:在struct后加關(guān)鍵字,例如redis中的sds簡單動態(tài)字符串的實現(xiàn):

    struct __attribute__ ((packed)) sdshdr16 {
        uint16_t len;
        uint16_t alloc;
        unsigned char flags;
        char buf[];
    }

聯(lián)合體

所有字段共用一段內(nèi)存,用于PHP中變量值的存儲(因為變量只有一種類型),也可以用來判斷機器的大小端問題。

宏定義

宏就是替換。

關(guān)于下面這段代碼的復(fù)雜宏替換問題,在PHP內(nèi)存管理3筆記中已經(jīng)有詳細解釋,此處不再贅述。

#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,
static const uint32_t bin_data_size[] = {
  ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)
};

關(guān)于C語言宏定義中的##等特殊符號的用法,參考:#define宏定義中的#,##,@#, 這些符號的神奇用法

PHP7中的基本變量

在PHP7中,所有變量都以zval結(jié)構(gòu)體來表示。一個zval是16字節(jié);在PHP5中,一個zval是48字節(jié)。

struct _zval_struct {
    zend_value value;
    union u1;
    union u2;
};

存儲變量需要考慮兩個要素:值與類型。

變量值的存放

在PHP7中,變量的值存在zend_value 這個聯(lián)合體中。只有整型和浮點型是直接存在zend_value中,其余類型都只存放了一個指向?qū)iT存放該類型的結(jié)構(gòu)體指針。這個聯(lián)合體共占用8字節(jié)。

typedef union _zend_value {
    zend_long         lval;    //整型
    double            dval;    //浮點
    zend_refcounted  *counted; //引用計數(shù)
    zend_string      *str; //字符串
    zend_array       *arr; //數(shù)組
    zend_object      *obj; //對象
    zend_resource    *res; //資源
    zend_reference   *ref; //引用
    zend_ast_ref     *ast; //抽象語法樹
    zval             *zv;  //內(nèi)部使用
    void             *ptr; //不確定類型,取出來之后強轉(zhuǎn)
    zend_class_entry *ce;  //類
    zend_function    *func;//函數(shù)
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww; //這個union一共8B,這個結(jié)構(gòu)體每個字段都是4B,因為所有聯(lián)合體字段共用一塊內(nèi)存,故相當(dāng)于取了一半的union
} zend_value;
變量類型的存放

在PHP7中,其變量的類型存放在zval中的u1聯(lián)合體中:

...
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,     /* 在這里用unsigned char存放PHP變量值的類型 */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)        /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
...

PHP7中所有的變量類型:

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                        1
#define IS_FALSE                    2
#define IS_TRUE                        3
#define IS_LONG                        4
#define IS_DOUBLE                    5
#define IS_STRING                    6
#define IS_ARRAY                    7
#define IS_OBJECT                    8
#define IS_RESOURCE                    9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                    11
#define IS_CONSTANT_AST                12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                    14
#define IS_ITERABLE                    19
#define IS_VOID                        18

/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                        17
#define _IS_ERROR                    20
PHP7中的字符串 字符串基本結(jié)構(gòu)

設(shè)計字符串存儲的數(shù)據(jù)結(jié)構(gòu)兩大要素:字符串值和長度。

PHP7字符串存儲結(jié)構(gòu)的設(shè)計:

struct _zend_string {
    zend_refcounted_h gc;         /*引用計數(shù),與垃圾回收相關(guān),暫不展開*/
    zend_ulong        h;          /* 冗余的hash值,計算數(shù)組key的哈希值時避免重復(fù)計算*/
    size_t            len;        /* 長度 */
    char              val[1];     /* 柔性數(shù)組,真正存放字符串值 */
};

由為什么存長度引申出二進制安全的問題。二進制安全:寫入的數(shù)據(jù)和讀出來的數(shù)據(jù)完全相同,就是二進制安全的,詳情見PHP字符串筆記

字符串寫時復(fù)制

看下面一段PHP代碼:



利用gdb調(diào)試這段代碼,觀察其引用計數(shù)情況。

在第一個echo語句處打斷點,并查看$a中zend_stinrg中的引用計數(shù)gc.refcount = 1(下簡稱refcount)。因為現(xiàn)在只有一個$a引用zend_string。

利用gdb的c命令繼續(xù)運行下一行PHP代碼$b = $a,然后觀察$a的zend_sting,我們發(fā)現(xiàn)$a引用的zend_string的refcount變?yōu)?:

查看此時的$b,發(fā)現(xiàn)引用的zend_string的refcount也是2,且地址均是0x7ffff5e6b0f0,說明$a與$b所引用的是同一個zend_string。

此時的內(nèi)存結(jié)構(gòu)如圖所示:

這樣做的優(yōu)點就是僅僅需要1個zend_string就可以存儲兩個PHP變量的值,而不是2個zend_string,節(jié)省了1個zend_string的內(nèi)存空間。

那么我們看接下來$b = "new string",這樣的話,$a和$b由于存儲的內(nèi)容不同,故不可以繼續(xù)引用同一個zend_string,這時就會發(fā)生寫時復(fù)制。我們繼續(xù)gdb調(diào)試,看一下是否符合預(yù)期:

給$b賦值后,觀察$a的存儲情況:

我們看到,此時$a所指向的zend_string的refcount變?yōu)榱?,接下來再看一下$b的存儲情況:

注意此時$b所指向的zend_string的refcount變?yōu)榱?(注意這里為什么是0而不是1呢?下面會講),而且b指向的zend_string的地址為0x7ffff5e6a5c8,與$a所指向的zend_string的地址0x7ffff5e6b0f0不同,說明發(fā)生了寫時復(fù)制,即由于字符串值的改變,被迫生成了一個新的zend_string結(jié)構(gòu)體,用來專門存儲$b的值;而$a指向的zend_string只是refcount減少了1,其余并未發(fā)生變化。

那么為什么$b所指向的zend_string的refcount是0呢,我們先給PHP中的字符串分個類:

常量字符串:在PHP代碼中硬編碼的字符串,在編譯階段初始化,存儲在全局變量表中,refcount一直為0,其在請求結(jié)束之后才被銷毀(方便重復(fù)利用)。

臨時字符串:計算出來的臨時字符串,是執(zhí)行階段經(jīng)過zend虛擬機執(zhí)行opcode計算出來的字符串,存儲在臨時變量區(qū)。

我們舉一個例子:



這里$a由于調(diào)用了time()函數(shù),所以最終的值是不確定的,是臨時字符串。

$b也可以叫做字面量,是被硬編碼在PHP代碼中的,是常量字符串。

我們畫一下最終$a與$b的內(nèi)存結(jié)構(gòu)圖:

由此我們可以清晰地看到,$a與$b不在引用同一個zend_string。那么我們給寫時復(fù)制下一個定義:給$b重新賦值而導(dǎo)致不能與$a共用一個zend_string的現(xiàn)象,叫做寫時復(fù)制。

PHP7中的數(shù)組

PHP7中的數(shù)組是一個hashtable,key-value對存儲于bucket中。

PHP7數(shù)組基本結(jié)構(gòu):

struct _zend_array {
    zend_refcounted_h gc;
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    consistency)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask;       //數(shù)組大小減一,用來做或運算,packed array初始值是-2,hash array初始值是-8
    Bucket            *arData;          //指針,指向?qū)嶋H存儲數(shù)組元素的bucket
    uint32_t          nNumUsed;         //使用了多少bucket,但是unset的時候這個值不減少
    uint32_t          nNumOfElements;   //真正有多少元素,unset的時候會減少
    uint32_t          nTableSize;       //bucket的個數(shù)
    uint32_t          nInternalPointer;
    zend_long         nNextFreeElement; //支持$arr[] = 1;語法,沒插入1個元素就會遞增1
    dtor_func_t       pDestructor;
};

typedef struct _zend_array HashTable;

此結(jié)構(gòu)在內(nèi)存中的結(jié)構(gòu)圖如下:

思考:為什么要存儲gc字段?因為gc字段冗余存儲了變量的類型,給任意一個變量,把它強轉(zhuǎn)成zend_refcounted_h類型,都可以拿到它的類型,zend_refcounted_h類型結(jié)構(gòu)如下:

typedef struct _zend_refcounted_h {
    uint32_t         refcount;            /* 引用計數(shù) */
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,    /* used for strings & objects */
                uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
        } v;
        uint32_t type_info;
    } u;
} zend_refcounted_h;

進行強制類型轉(zhuǎn)換之后,通過取該變量的u.type字段,就可以拿到當(dāng)前變量的類型了。

我們接著看一下bucket的結(jié)構(gòu):

typedef struct _Bucket {
    zval              val;   //元素的值,注意這里直接存了zval而不是一個zval指針
    zend_ulong        h;     //冗余的哈希值,避免重復(fù)計算哈希值
    zend_string      *key;   //元素的key值,指向一個zend_string結(jié)構(gòu)體
} Bucket;

思考如果利用$arr[] = 1;語法進行數(shù)組賦值,key字段的值是多少?答案是0x0,就是一個空指針。

hashtable的問題:哈希沖突,解決沖突的方法有開放定制法和鏈地址法,常用的是鏈地址法。

PHP7中并沒有采用真正的鏈表結(jié)構(gòu),而是利用數(shù)組模擬鏈表。這個時候需要在Bucket數(shù)組之前額外開辟一段內(nèi)存空間(叫做索引數(shù)組,每個索引數(shù)組的單元叫一個slot),來存儲同一hash值的第一個bucket的索引下標(biāo)。

看一個簡單的數(shù)組查找過程:

經(jīng)過time33哈希算法算出哈希值h

計算出索引數(shù)組的nIndex = h | nTableMask = -7(假設(shè)),這個nIndex也別稱做slot

訪問索引數(shù)組,取出索引為-7位置上的元素值為3

訪問bucket數(shù)組,取出索引為3位置上的key,為x,發(fā)現(xiàn)并不等于s,那么繼續(xù)查找,訪問val.u2.next指針,為2

取出索引為2位置上的key,為s,發(fā)現(xiàn)正好是我們要找的那個key

取出對應(yīng)的val值3

注意如果bucket的存儲空間滿了,需要重新計算和nIndex(即slot)的值并將值放到正確的bucket位置上,這個過程也叫做rehash。

具體的插入過程詳見PHP基本變量筆記的文章末尾。

PHP7中的數(shù)組分為兩種:packed array與hash array。

packed array:

key是數(shù)字,且順序遞增

位置固定,如訪問key是0的元素,即$arr1[0],就直接訪問bucket數(shù)組的第0個位置即可(即arData[0]),這樣就不需要前面的索引數(shù)組。

如果不滿足上述條件,就是hash array

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/31324.html

相關(guān)文章

  • 【每日學(xué)習(xí)記錄】使用錄像設(shè)備記錄每天學(xué)習(xí)

    摘要:在這里使用學(xué)而思網(wǎng)校的錄像設(shè)備,記錄每天學(xué)習(xí)的內(nèi)容閆昌李樂階段李樂李樂李樂李樂李樂李樂馬運運李樂李樂李樂源碼集群閆昌源碼閆昌源碼主從復(fù)制李樂源碼施洪寶源碼施洪寶韓天 在這里使用學(xué)而思網(wǎng)校的錄像設(shè)備,記錄每天學(xué)習(xí)的內(nèi)容: 2019-06-24 ~ 2019-06-28 06-27 nginx by 閆昌 06-26 nginx module by 李樂 06-25 nginx http ...

    szysky 評論0 收藏0
  • 【每日學(xué)習(xí)記錄】使用錄像設(shè)備記錄每天學(xué)習(xí)

    摘要:在這里使用學(xué)而思網(wǎng)校的錄像設(shè)備,記錄每天學(xué)習(xí)的內(nèi)容執(zhí)行潘森執(zhí)行潘森執(zhí)行潘森趙俊峰紅黑樹景羅紅黑樹景羅配置三叉樹田志澤新建模塊馬運運配置田志澤田志澤田志澤李樂田志澤田志澤文件系統(tǒng) 在這里使用學(xué)而思網(wǎng)校的錄像設(shè)備,記錄每天學(xué)習(xí)的內(nèi)容: 2019-07-15 ~ 2019-07-19 07-18 nginx http 執(zhí)行 by 潘森 07-17 nginx http 執(zhí)行 by 潘森 07...

    pkhope 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<