PHP 7底层设计与源码实现
上QQ阅读APP看书,第一时间看更新

3.1 基本知识

为了更好地理解PHP 7中zval的实现,本节先介绍一下相关的基本知识,包括什么是数据类型,PHP 7中都有哪些变量类型,另外会介绍与变量存储相关的堆和栈的基本知识。

3.1.1 数据类型

数据类型是一种为了对数据(或数据值)进行分类而由用户定义的类型,在数据结构中的定义是一个值集合以及定义在这个值集合上的一组操作。

基本的数据类型有int、double、long、char及各种指针类型。在C语言中,使用变量时,提前定义变量并指定变量类型,而在PHP中变量不需要指定类型,3.2节将会讲解弱类型的实现。

C语言的数据类型在不同的操作系统中长度不同,举个例子,x86-64系统架构下,一个char类型的数据占1个字节,一个int类型的数据占4个字节,一个指针类型的数据占8个字节,一个long类型的数据占8个字节,可以在gdb下使用sizeof打印验证:

    (gdb) p sizeof(char)
    $1 = 1
    (gdb) p sizeof(int)
    $2 = 4
    (gdb) p sizeof(long)
    $3 = 8
    (gdb) p sizeof(char*)
    $4 = 8
    (gdb) p sizeof(void*)
    $5 = 8

注意

类型所占空间大小与系统架构有关,可以使用show architecture来查看:

    (gdb) show architecture
    The target architecture is set automatically (currently i386:x86-64)

也可以使用set architecture i386:x86-64来设置:

    (gdb) set architecture i386:x86-64
    The target architecture is assumed to be i386:x86-64

3.1.2 结构体与联合体

结构体是使用struct定义的结构,比如定义一个如下的结构体:

    struct test{
        char a; //1
        int b;  //4
        long c; //8
        void* d; //8
        int e;  //4
        char* f; //8
    };

在代码中标记了每个成员的大小,那么结构体的总大小是1+4+8+8+4+8=33吗?

下面来打印一下:

    (gdb) p sizeof(struct test)
    $1 = 40

为什么是40而不是33呢?这里面涉及结构体对齐问题,在笔者的机器上,结构体是按照8字节对齐的,那么可以画出对齐后占用的字节数,如图3-1所示。

图3-1 结构体

从图3-1中可看出,虽然char a只占了1字节,int b只占了4字节,但是long c并不是紧跟着b,而是根据8字节对齐后,c和b之间空了3字节。同样,char* f和int e之间也空了4字节,因此总大小为40字节。虽然浪费了7字节,但得益于内存对齐,存取速度会更快。这是结构体对齐的基础。

接下来讨论一下联合体(union),联合体的定义跟结构体类似,定义一个如下的联合体:

    union test{
        char a; //1
        int b;  //4
        long c; //8
    };

同样打印一下这个联合体的大小:

    (gdb) p sizeof(union test)
    $1 = 8

那么联合体是怎样的一种格式呢?它复用了同一块内存,如图3-2所示。

图3-2 联合体

从图中可以看出,a、b和c共用同一块内存,修改a,也会影响b和c的值,同时可以知道联合体的大小为其最大成员的大小,比如图3-2中联合体test的大小为其最大的成员long c的大小,也就是8。

这里简单学习了结构体和联合体的区别,以及结构体的对齐,接下来再回顾一下程序执行时的堆和栈相关的基本知识。

注意

联合体是成员变量共享一块内存,可以根据使用确定含义;而结构体是不共享的,成员变量不共享一块内存。另外,结构体存在对齐问题。

3.1.3 堆和栈的基本知识

程序执行时的内存布局主要如下:

图3-3 程序的堆和栈

1)栈区(stack)——存储参数值、局部变量,维护函数调用关系等。

2)堆区(heap)——动态内存区域,随时申请和释放,程序自身要对内存泄漏负责。

3)全局区(静态区)——存储全局和静态变量。

4)字面量区——常量字符串存储区。

5)程序代码区——存储二进制代码。

程序的堆和栈如图3-3所示。

接下来写一段C代码来理解一下各变量分别存在哪个段区,代码如下:

    int a=0;     //全局初始化区
    char *p1;    //全局未初始化区
    main()
    {
      static int b=0;    //全局(静态)初始
    化区
      int c; //栈
      char d[]="abc";    //栈
      char *p2;          //栈
      char *p3 = "hello";    //hello\0在常量区,p3在栈上
      p1 = (char*)malloc(10);
      p2 = (char*)malloc(20); //分配得来的10和20字节的区域就在堆区
      strcpy(p1, "hello");     //hello\0放在常量区,编译器可能会将它与p3所指向的"hello"优化成
          一个地方
    }

总体来讲,栈上的变量是局部的,随着局部空间的销毁而销毁,由系统负责。。

堆上的变量可以提供全局访问,需要自行处理其生命周期。

这一节学习了数据类型、结构体、联合体,以及程序运行时的堆和栈信息等基本知识,接下来讨论一下PHP中的变量。