【PHP7源碼學習】2019-03-08 PHP內存管理2筆記

baiyanphp

所有視頻:https://segmentfault.com/a/11...segmentfault

源視頻地址:http://replay.xesv5.com/ll/24...數組

複習

  • 宏的用法:
typedef struct _zend_alloc_globals {
    zend_mm_heap *mm_heap;
} zend_alloc_globals;
...
# define AG(v) (alloc_globals.v)
static zend_alloc_globals alloc_globals;
  • 這個帶有參數的宏取得zend_alloc_globals結構體類型裏的zend_mm_heap結構體字段,如AG(mm_heap) = alloc_globals.mm_heap
  • 宏就是替換。

結構體與結構體內存對齊

結構體

  • 先看一段結構體代碼struct.c:
#include <stdio.h>
int main() {
    struct a{
        char a;
        int b;
        long c;
        void *d;
        int e;
        char *f;
    }s;
    s.a = 'c';
    s.b = 1;
    s.c = 22l;
    s.d = NULL;
    s.e = 1;
    s.f = &s.a;
    printf("sizeof struct a is %d", (int)sizeof(s));
}
  • 編譯:gcc -g struct.c -o struct
  • 運行:./struct,sizeof(a)的打印結果爲:40
  • 對這個結構體進行gdb調試:

  • 在這裏咱們能夠看到第一行是全部結構體變量的初始值,注意指針變量是一個隨機的地址,在給s.d賦值的過程當中,地址變成了0x0,它是一個特殊的地址值,表明NULL。
  • 除此以外,咱們注意到結構體s的地址和a變量的地址是相同的。
  • 用表達式表示以上結論:&s = &s.a = f或者s = s.a = *f

結構體內存對齊

  • 核心結論:它是編譯器作的優化,空間換時間的思想。
  • 對齊方式:按照結構體全部字段的最小公倍數作對齊(最小公倍數是8B就按8B對齊;是4B就按4B對齊),且與結構體字段的排列順序是相關。如圖:

  • 咱們利用gdb驗證一下以上的結論,仍是利用上述一樣的代碼:

  • 咱們看到變量a的起始地址是150,而b的地址是154,顯然作了對齊。若是不對齊,b的地址應該爲151,說明a和b中間空了3B的大小。c的地址是158,就是下一個8B的起始地址,而d的地址是160(注意這裏是16進制,158+8 = 160的時候纔會進位),證實了c佔用了8B,下面d變量也同理佔用了8B。注意e變量,它是一個int,若是不對齊應該只佔用4B。而它佔用了170(f的起始地址)- 168(e的起始地址) = 8B,因此必定是作了內存對齊的。
  • 注意一個特例:若是b是一個char類型,那麼是直接緊跟在上一個char後面,如圖:

  • 注意這裏的地址均爲邏輯地址,每次編譯後的邏輯地址是相同的,而物理地址是不一樣的。
  • 注意:若是調換順序,把b和c調換位置,就會變成8B(a)+8B(c)+8B(b)+8B(d)+8B(e)+8B(f) = 48B
  • 注意:必定是全部字段的最小公倍數是幾字節,就按幾字節對齊,咱們看一下結構體中只有char類型變量的狀況:
#include <stdio.h>
int main() {
    struct a{
        char a;
        char b;
        char c;
    }s;
    s.a = 'c';
    s.b = 'b';
    s.c = 'a';
    printf("sizeof struct a is %d\n", (int)sizeof(s));
}
  • 這個結構體中只有char類型變量
  • 編譯運行,輸出sizeof(s)的結果爲:3
  • 爲何不是4或者8?由於1,1,1的最小公倍數就是1,就按照1B來對齊,而不是4B、8B
  • 同理,若是都是int類型的變量,那麼sizeof(s)的結果爲12,也很好理解了

聯合體

  • 核心結論:全部聯合體字段共用一塊內存空間。整個聯合體佔用的空是全部字段單獨佔用空間大小中取佔用空間大小最大的字段。
  • 一樣先看一段代碼:
#include <stdio.h>
int main() {
    union a{
        char a;
        int b;
        long c;
        void *d;
        int e;
        char *f;
    }s;
    s.a = 'c';
    s.b = 1;
    s.c = 22l;
    s.d = NULL;
    s.e = 1;
    s.f = &s.a;
    printf("sizeof struct a is %d", (int)sizeof(s));
}
  • 這段代碼與上段結構體的代碼只將struct修改成union,其餘均不變
  • 編譯運行,輸出sizeof(a)的結果爲:8
  • 咱們利用gdb調試一下這段代碼:

  • 咱們能夠看到,後面的變量一賦值,就會覆蓋前面的變量值
  • 再看一下每一個變量的地址,咱們能夠清楚地看到,全部變量的起始地址都是同樣的。

其餘

  • 思考:一個void *類型的變量,可否直接取它的內容?答案:不能夠,其餘有類型的指針變量能夠取內容是由於記錄了當前類型的長度,而void *類型沒有長度,沒法直接取,除非使用強制類型轉換或者指定長度。
  • 延伸:PHP全部變量基於zval,zval就是由3個聯合體組成(zend_value,u1,u2)這裏不展開
  • 大小端:網絡

    • 大端:也叫高尾端,即數據尾端(低位)放在高地址
    • 小端:也叫低尾端,即數據尾端(低位)放在低地址
    • 網絡字節序是大端的
    • 網絡字節序是大端的,因此小端機器須要對收到或發出的數據包進行大小端的轉換

  • 如何判斷是大端仍是小端:參考:判斷機器大小端的兩種實現方式
  • 利用指針:int爲4個字節,經過強轉成char類型,取低1個字節,若是剛好是0x78,那就說明低1個字節剛好存在低地址,爲小端。若是取低1個字節的結果是0x12,低1個字節存在高地址,爲大端。
int main() {
    int i = 0x12345678;
    if (*(char*)&i == 78) {
        printf("小端");
    } else {
        printf("大端");
    }
}
  • 利用聯合體:本質思想和指針方法相同,利用了聯合體共用同一塊存儲空間的特性。
int main() {
    union w{
        int a;
        char b;
    }c;
    c.a = 1;
    if (c.b == 1) {
        printf("小端");
    } else {
        printf("大端");
    }
}
  • 利用gdb觀察PHP內存分配時的狀況,示例代碼:
<?php
$a = 1;
echo $a;
  • gdb php並在_emalloc()處打斷點,運行代碼:

  • 咱們能夠清晰地看到mm_heap這個變量,它在small、large內存中存儲一些額外的page分配信息等等。其中比較重要的size、peak、free_slot、main_chunk等變量。free_slot數組暫時仍是空。再看mm_heap中的main_chunk字段,它來表示一個chunk,是一個雙向鏈表。第一個字段heap是一個指向zend_mm_heap的指針,能夠快速找到第一個記錄信息的page,而且能夠發現它的地址和上面直接打印alloc_globals.mm_heap的地址是相同的。再觀察free_map字段,它由8個個uint64類型組成,表明各個page的使用狀況,這裏第1個page存了zend_mm_heap結構體,已經被使用。再看map字段,它是一個數組,大小爲512,每一個都是uint32類型,打印數組第一項的值,以16進製表示爲0x40000001,表明large內存,最後一位是1,表明分配1個page。
  • 使用c命令繼續運行:

  • 咱們能夠看到free_slot上有3項已經不是0x0了,說明上次分配過small內存。且如今free_map的值發生了變化,而map說明第一、二、三、四、9頁已經被使用了,那麼爲何中間有幾個0呢,是由於第4個map值,按照16進制打印爲0x40000005,是large內存,且須要分配5個頁,因此後面的4個頁都是0。
相關文章
相關標籤/搜索