PHP7基本变量

1. 结构体与联合体

在这里插入图片描述

// 包含头文件
// #include<stdio.h> 就是一条预处理命令,它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作
#include<stdio.h> 
// 函数就是实现代码逻辑的一个小的单元
// 主函数 在最新的C标准中,main函数前的类型为int而不是void
int main()
{
    /*定义结构体*/ 
    struct _s{
        char a;
        int b;
        long c;
        void* d;
        int e;
        char* f;
    } s;
    /*
     成员变量赋值
    */
    s.a = 'a'; //地址 0x7fffffffe310
    s.b = 1; //地址 0x7fffffffe314
    s.c = 2; //地址 0x7fffffffe318
    s.d = NULL; //地址 0x7fffffffe320
    s.e = 3; //地址 0x7fffffffe328
    s.f = &s.a; //地址 0x7fffffffe330
    
    printf("size: %d", sizeof(s)); // size: 40
    return 0;
}

结构体是进行内存对齐的, 64位一般是按照8个字节对齐, 每个成员根据自身占用空间, a占用1个字节后会空出3个字节然后b占用4个字节, c和d和f正好占用8个字节, e占用4个字节, 则该结构体共占用40个字节的大小
在这里插入图片描述

#include<stdio.h> 
// 一个C程序有且只有一个主函数,即main函数
// C程序就是执行主函数里的代码,也可以说这个主函数就是C语言中的唯一入口
int main()
{
    /*定义联合体*/ 
    union _u{
        char a;
        int b;
        long c;
        void* d;
        int e;
        char* f;
    } u;
    /*成员变量赋值*/
    u.a = 'a'; //地址 0x7fffffffe330
    u.b = 1; //地址 0x7fffffffe330
    u.c = 2; //地址 0x7fffffffe330
    u.d = NULL; //地址 0x7fffffffe330
    u.e = 3; //地址 0x7fffffffe330
    u.f = &u.a; //地址 0x7fffffffe330
    
    printf("size: %d", sizeof(u)); // size: 8
    return 0;
}

联合体则是共用内存, 以成员占用的最大内存大小对齐, 成员a占用1个字节, 然后b占用4个字节会复用a的内存, c占用8个字节也会复用b的空间, 使用的是同一块内存空间, 即后边的值会覆盖掉前面的值

2. 宏定义

宏 即 “替换”

define ZEND_ENDIAN_LOHI_4(a,b,c,d) d;c;b;a;

// 以下4个变量
ZEND_ENDIAN_LOHI_4(
	zend_uchar flags,
	zend_uchar nApplyCount,
	zend_uchar nIteratorsCount,
	zend_uchar consitency
);
// 使用宏之后, 即4个变量会倒过来
consitency,nIteratorsCount,nApplyCount,flags
3. 大小端

在这里插入图片描述
PHP要在不同的机器上运行, 而有的机器是大端机器 ( 高位放在低地址, 低位放在高地址 ), 有的是小端机器 ( 低位放在低地址, 高位放在高地址 ), 所以取值要区分大小端

#include<stdio.h> 
// 定义一个宏
#define MY_MACRO " a %s"
int main()
{
    
    char *m = "macro";
    
    printf("this is" MY_MACRO, m);
    
}
#include<stdio.h> 
void func1()
{
    // 定义16进制 12345678
    int i = 0x12345678; // 4个字节
    // 0x78 0x56 0x34 0x12
    if(*((char*)&i) == 0x12)
    {
        printf("func1 big endian");
    }else{
        printf("func1 little endian");
    }
    
}
void func2()
{
    // 定义一个联合体
    union _u{
        int x; // 4个字节
        char y; // 1个字节
    } u;
    
    u.x = 1; // 4个字节
    // 0000 0000 0000 0000 0000 0000 0000 0001
    if(u.y == 1)
    {
        printf("func1 little endian");
    }else{
        printf("func1 big endian");
    }
    
}
4. 小而巧的zval

php-7.413源码包, 找到Zend文件夹zend_types.h

typedef struct _zval_struct     zval;
typedef struct _zend_string     zend_string;
typedef struct _zend_array      zend_array;
struct _zval_struct {
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				union {
					uint16_t  extra;        /* not further specified */
				} u)
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
		uint32_t     opline_num;           /* opline number (for FAST_CALL) */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
		uint32_t     access_flags;         /* class constant access flags */
		uint32_t     property_guard;       /* single property guard */
		uint32_t     constant_flags;       /* constant flags */
		uint32_t     extra;                /* not further specified */
	} u2;
};
typedef union _zend_value {
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;

zval为结构体, 包含3个成员变量, 联合体zend_value 别名 value, 联合体 别名 u1, 联合体 别名 u2, value最大的内存为指针类型8字节, u1由两个成员变量构成, 联合体共用内存, 最长4个字节, 结构体v占4个字节(2个uchar, uint16), 另外一个变量也占4个字节(uint32), u2也为联合体, 最长4个字节 (uint32)
在这里插入图片描述
zval结构体以8字节对齐, 最长16个字节, 占用空间很小, PHP虽然为弱类型语言, 但是底层也是会标记类型, 通过zval来实现, zval的成员变量value, 定义了多种类型, 然后通过zval的成员变量u1来区分变量的不同类型, u1有一个zend_uchar类型( 1个字节 )的type变量, 通过不同的值来区分不同的类型
在这里插入图片描述
若type=4, zval取的就是zend_long类型的lval, 若type=6, zval取的就是zend_string类型的*str( 地址指针 ), 根据地址获取内容, 进而再根据字符串结构体的len和val取字符串
在这里插入图片描述
字符串类型zend_string结构体, 成员变量gc也是一个结构体 ( 引用计数 ), 占8个字节, h占8个字节, len占8个字节 ( 字符串长度 ), val[1]占1个字节( val为真正存储字符串的柔性数组, val[1]占位, 后续则存储字符串内容 ), 由于字节对齐所以也占8个字节, 总共32个字节, zval根据该结构体的len和val来取字符串内容

在这里插入图片描述
对于简单类型 ( int, double ), 则是直接复制, 先将10赋值给a, 把a赋值给b时, 则是直接将10拷贝一份给b, 因为只用了16个字节的空间存储, 浪费较少
在这里插入图片描述
对于复制类型, 则是写时复制
在这里插入图片描述
将字符串复制给变量a时, 底层通过zval实现, zval结构体的成员变量u1的type为6, value.str存储的则是字符串真正的内存地址, 即指向一个zend_string结构体, 其引用计数为1
在这里插入图片描述
当把变量a赋值给b时, 则b的zval中的value.str也指向a的内存地址, 只不过该字符串zend_string结构体中的引用计数加1
在这里插入图片描述
当给变量b重新赋新值时, 则b的zval中的value.str指向另一个字符串存储的内存地址, 即一个新的zend_string结构体, 且变量a指向的zend_string的引用计数减1
在这里插入图片描述
数组 ( Hash Table ) 类型

// 定义了一个_zend_array结构体, 别名 HashTable
typedef struct _zend_array HashTable;

typedef struct _Bucket {
	zval              val; 
	zend_ulong        h;    /* hash value (or numeric index)   */
	zend_string      *key;  /* string key or NULL for numerics */
} Bucket;

typedef struct _zend_refcounted_h {
	uint32_t         refcount;			/* reference counter 32-bit */
	union {
		uint32_t type_info;
	} u;
} zend_refcounted_h;

struct _zend_array {
	zend_refcounted_h gc;
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    flags,
				zend_uchar    _unused,
				zend_uchar    nIteratorsCount,
				zend_uchar    _unused2)
		} v;
		uint32_t flags;
	} u;
	uint32_t          nTableMask;
	Bucket           *arData;
	uint32_t          nNumUsed;
	uint32_t          nNumOfElements;
	uint32_t          nTableSize;
	uint32_t          nInternalPointer;
	zend_long         nNextFreeElement;
	dtor_func_t       pDestructor;
};

_zend_array结构体, 成员变量gc为8个字节的结构体, 引用计数, 与垃圾回收相关, 成员变量u为4个字节的联合体, uint32_t类型的nTableMask, 用来做散列, 计算落在哪个桶里面, 通过键值对的key算出一个散列值, 然后与nTableMask做或运算, 保证元素散列到指定大小的Bucket里, arData中前面存储的是一个个的int32, 记录每个key对应的位置, 后边存储的是一个个Bucket结构体, 每个Bucket里面存储是真正的键值对, ( 成员变量val对应key=>value键值对中的value, 为zval结构体类型, h为通过哈希计算出来的哈希值, 字符串类型的key对应键值对中的key ), 首先通过散列能知道对应的键值对在哪个Bucket上, 然后若出现冲突再通过前面int32记录的位置建立一个逻辑上的列表来解决冲突 , nTableSize为Bucket数组的个数, 初始化时为8, 后续会以2倍值变化, 即16
在这里插入图片描述
数组a中先存入索引为foo的值1, 首先foo通过哈希运算得到一个哈希值h, h与nTableMask ( 初始化时, Packed Array的值为-2, 即两个位置, Hash Array的值为-8, 后续会根据插入值数量以2倍值变化, 即-16 )做或运算, 假如得到nIndex=-7, 为了保证数组插入时的顺序, 保证数组foreach时可以根据插入顺序遍历出来, 所以会存在第0个位置, 前面会在索引数组-7的位置上写入0, 后边在位置0的Bucket中存储键值对 ( 其中key实为内存地址, 指向真正的zend_string, val为具体值), nNumUsed和nNumOfElements都加1
若是要查询数组a中foo的值, 先将foo进行哈希运算然后与nTableMask或运算, 得到-7, 然后获取-7位置上的值, 根据该值去对应位置上的Bucket, 最后在判断两个key是否匹配, 匹配则返回val的值
在这里插入图片描述

数组a中插入没有key的value为2的值, 没有key时需要用到nNextFreeElement ( 初始化值为0 ), 则h为0, 与nTableMask进行或运算, 得到nIndex为-8, 把该数组元素放到第1个位置的Bucket上, 然后前面索引数组-8位置的值写入1, 同样nNextFreeElement , nNumUsed和nNumOfElements都加1
取值时同样与nTableMask或运算, 然后在索引数组上得到位置, 根据这个位置去对应的Bucket得到值
在这里插入图片描述
数组a中插入key为s的值3, 假如s经过哈希运算后与nTableMask进行或运算, 得到nIndex也是-7, 与key为foo的元素产生冲突, 此时仍会将该元素存储到位置为2的Bucket上, 然后在索引数组-7的位置写上2, 同时Bucket成员变量val为zval类型, 其成员变量u2中有一个变量next, 会记录上一个被覆盖的索引数组的位置值, 类似逻辑链表, 同样nNumUsed和nNumOfElements加1
当获取foo的值时, 先找到索引数组-7位置上的值为2, 然后找到对应位置的Bucket, 发现key不匹配, 然后继续找到val.u2.next的值0, 再去0位置的Bucket, key匹配然后获取val值
在这里插入图片描述
数组a中再插入key为x的值4, 假如x哈希计算后与nTableMask或运算, 得到nIndex也为-7, 将该元素存储到位置为3的Bucket上, 然后索引数组-7位置上写入3, 同时zval类型的val, 成员变量u2的next, 记录上一个被覆盖的索引数组的位置值2
当获取foo的值时, 先找到索引数组-7位置上的值为3, 然后找到对应位置的Bucket, 发现key不匹配, 然后继续找到val.u2.next的值2, 再去2位置的Bucket, key仍不匹配, 然后继续找到val.u2.next的值0, 再去0位置上的Bucket, key匹配, 获取val值
在这里插入图片描述
Packed Array, 前面是索引数组, 后边是Bucket, 其元素的key是从0递增的整数, 所以不需要再计算哈希值, 前面的索引数组用不到, 只保留了2个位置, 取值时直接根据key来找对应位置的Bucket
Hash Array, 前面是索引数组, 后边是Bucket, 其元素的key没有规律, 任意字符串或者数字, 需要对key进行哈希计算, 并使用前面的索引数组维护后边Bucket位置
假如同时向以上两种数组中插入10万个元素, 最后占用的内存大小, Hash Array要大于Packed Array, 因为Hash Array需要前面的索引数组维护位置 ( 10万个元素需要10万位置, 每个位置是一个int ), 则会占用更多的内存空间
所以实际工作中, 同样可以解决问题的, 尽量使用 Packed Array
在这里插入图片描述

<?php
// 初始化
$arr = []; // 初始化默认为Packed Array, 其nTableMask为-2, 索引数组保留2个位置
// 无key赋值
$arr[] = 'foo'; // 写入在第0个位置的Bucket上, 无key, h为0, 取值时直接根据key来获取
// 数字key赋值
$arr[2] = 'abc'; // 写入在第2个位置的Bucket上, 无key, h为2, 取值时直接根据key来获取, 第1个位置空置
// 字符串key赋值
$arr['a'] = 'bar'; // 由Packed Array变为Hash Array, 其nTableMask为-8, 索引数组暂时保留8个位置, 元素位置变动, 把原先第2个位置Bucket的元素放到了第1个位置的Bucket, 该元素放到现在第2个位置的Bucket, 且key为a, 并根据a哈希计算后与nTableMask或运算得出的值, 在前面索引数组对应位置上写入Bucket的位置值2, h为a哈希后的值, 取a的值时, 先哈希再或运算然后到对应的Bucket获取
// 无key赋值
$arr[] = 'xyz'; // 写入在第3个位置的Bucket上, 无key, h为3, 取值时直接根据key来获取
// 已存在的key赋值
$arr['a'] = 'foo'; // 对key哈希计算与nTableMask或运算, 找到索引数组对应位置的值, 然后替换到对应位置Bucket的元素
// 根据key查找
$arr['a'];
// 删除key
unset($arr['a']); // 找到对应位置的Bucket, Bucket中的h没变, key没变, 然后把zval类型的val中, 成员变量u1的type值, 不再是6 (IS_STRING )而是变为0 ( IS_UNDEF ), 即没有意思, 所以unset并不是将该元素去掉, 后边可以再进行覆盖

>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

敲代码der

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值