PHP内核之zend_parse_parameters

函数介绍

关于zend_parse_parameters函数的使用,网上已经有很多的介绍了。函数的原型定义在zend_API.c中,为:

ZEND_API int zend_parse_parameters(int num_args,const char *type_spec,....),

num_args表示参数的个数,通过使用ZEND_NUM_ARGS()获取而不手动计算传入。第二个参数type_spec是规定传入值的类型,比较常用的就是s代表字符串,l代表long int。


例子的疑惑

网上最多的一个列子如下(最权威的例子可以到php源码中的README.PARAMETER_PARSING_API中查看,有详细介绍):

PHP_FUNCTION(test){
    char *s;
    size_t s_len;
if(zend_parse_paramters(ZEND_NUM_ARGS(),"s",&s,&s_len) == FAILURE){
    return;
}
php_printf("we get %s",s);
}

上面例子是针对字符串赋值的,对于一个C小白来说,疑惑的是为什么定义一个字符串指针变量s后,传入zend_parse_parameters函数中的是指针变量s的地址。如果是针对long int数据赋值,那么语法应该是这样的:

PHP_FUNCTION(test){
    zend_long a;
if(zend_parse_paramters(ZEND_NUM_ARGS(),"l",&a) == FAILURE){
    return;
}
php_printf("we get %d",a);
}

这个我可以理解,就是常见的通过向函数传入外部变量的地址,达到修改外部变量的效果。那么为什么字符串和整形传递的类型不一样,这里涉及到了变量在php7中的存储方式,也就是ZVAL结构。首先我们来看整型和字符串型在zend_parse_parameters函数中分别是怎么处理的,主要的区别就在于最后对参数赋值时(两个函数均定义在zend_API.h中)

long int:

zend_long *p = va_arg(*va,zend_long *);
zend_parse_arg_long(arg,p,is_null,check_null,c=='L');

string:

char **p = va_arg(*va,char **);
zend_parse_arg_string(arg,p,pl,check_null)

从这里定义的变量p可以看出,字符串中获取的参数就必须是双重指针了。但这并不是理由,真正的原因在于zval中string和long int的存储方式。让我们先看zend_parse_arg_long函数:

int zend_oarse_arg_long(zavl *arg,zend_long *dest,zend_bool *is_null,int check_null,int cap){
if(EXPECTED(Z_TYPE_P(arg) == IS_LONG)){
*dest = Z_LVAL_P(arg);}}

Z_TYPE_P宏的功能是根据zval指针变量判断该zval的数据类型。Z_TYPE_PP则是根据指向zval指针变量的指针变量判断zval类型。如果类型正确,则执行Z_LVAL_P(arg),这个宏的展开为:

#define Z_LVAL_P(zval_p)   *(zavl_p).value.lval

关于这些zval相关的宏,大多定义在zend_types.h文件中,一些特定类型的就在对应的文件中,比如ZSTR_VAL(zstr)定义在zend.string.h文件中。那么对于long int这个例子:

*dest = Z_LVAL_P(arg)
等价于
zend_long a = *(zavl_p).value.lval

这里需要明白,对于简单数据类型:long,int等类型,是直接存储在zval结构中的value中的:

zval简图如下:

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

zend_value简图如下:

typdef union _zend_value {
    zend_long   lval;
    zend_string   *str;
    zend_array   *arr;
    .....;
    struct {
    ...
    }ww;
}zend_value;
从上面的zval结构可知,long类型可以直接在zval.value.lval中获取,而一些复合结构如string,arr则需要再根据存储的地址去取值。


下面再看函数zend_parse_arg_string:

int zend_parse_arg_string(zval *arg,zend_string **dest,int check_null){
    zend_string *str;
  if(!zend_parse_arg_str(arg,&str,check_null)){
      return 0;
    }
      *dest = ZSTR_VAL(str);
      *dest_len = ZSTR_LEN(str);
}

函数中,先将参数的值通过函数zend_parse_arg_str进行解析,即通过zval指针变量获取到zval中vlaue.str的值。

zend_parse_arg_str定义如下:

int zend_parse_arg_str(zval *arg,zend_string **dest,int check_null){
   if(EXPECTED(Z_TYPE_P(arg) == IS_STRING)){
   *dest = Z_STR_P(arg);
}
}

宏Z_STR_P(arg)等价于*(arg).value.str。所有当前的str指针变量指向了一个zend_string结构体。

最后的ZSTR_VAL和ZSTR_LEN宏就是对zend_string结构体中的数据进行提取。

ZSTR_VAL(str) = (str)->val;
ZSTR_LEN(str) = (str)->len

其实通过看zend_string结构体的结构你就知道这个宏是怎么回事了。如下:

struct _zend_string {
    zend_refcounted_h gc;
    zend_ulong h;
    size_t len;
    char val[1];
}

所以,像string,array等复合类型数据最后传递给参数的都是一个地址,那参数s也肯定得是一个指针变量来存储一个地址,而对于int,long等简单类型,最后传递给参数的是一个值。因此定义一个对应的类型参数就可以用来接收数据了。

至此,关于zend_parse_parameters的介绍就告一段落。

猜你喜欢

转载自blog.csdn.net/github_38392025/article/details/79470923