>  기사  >  백엔드 개발  >  [번역][php 확장 개발 및 임베디드] 2장 - 변수의 내부와 외부

[번역][php 확장 개발 및 임베디드] 2장 - 변수의 내부와 외부

黄舟
黄舟원래의
2017-02-09 11:15:161365검색

변수의 내부와 외부

모든 프로그래밍 언어에 공통된 기능 중 하나는 정보를 저장하고 검색하는 것입니다. PHP도 예외는 아니지만 사용하기 전에 모든 변수를 정의해야 합니다. 고정되어 있지만 PHP를 사용하면 프로그래머가 변수를 사용할 때 변수를 생성할 수 있고, 어떤 유형의 언어로도 표현할 수 있는 정보를 저장할 수 있습니다. 또한 필요할 때 자동으로 변수 유형을 변환할 수도 있습니다.

사용자를 사용했기 때문에- space PHP, 이 개념이 "약한 타이핑"이라는 것을 알아야 합니다. 이 장에서는 이 정보를 PHP의 상위 언어인 c(C의 유형은 엄격함)에서 볼 수 있습니다.

물론, 어떻게 인코딩되나요? 데이터 인코딩은 작업의 절반에 불과합니다. 이러한 모든 정보를 추적하려면 각 변수에도 레이블과 컨테이너가 필요합니다. 사용자 공간 관점에서 이를 변수 이름과 범위의 개념으로 생각할 수 있습니다.

데이터 유형

PHP의 데이터 저장 단위는 Zend Value라고도 하는 zval입니다. Zend/zend.h에 정의된 4개의 멤버 본문만 있는 구조이며 형식은 다음과 같습니다.

typedef struct _zval_struct {  
    zval_value  value;  
    zend_uint   refcount;  
    zend_uchar  type;  
    zend_uchar  is_ref;  
} zval;

우리는 대부분의 멤버의 기본 저장 유형을 직관적으로 추측할 수 있습니다: 부호 없는 정수의 refcount, 부호 없는 문자의 유형 및 값 그리고 값 멤버는 실제로 PHP5에서 공용체로 정의된 구조입니다.

typedef union _zvalue_value {  
    long                    lval;  
    double              dval;  
    struct {  
        char        *val;  
        int     len;  
    }                   str;  
    HashTable           *ht;  
    zend_object_value       obj;  
} zvalue_value;

Union을 사용하면 Zend는 단일 통합 구조를 사용하여 다양한 유형의 데이터를 PHP 변수에 저장할 수 있습니다.

zend는 현재 다음에 나열된 8개의 데이터 유형을 정의합니다. 테이블:


IS_RESOURCE여기 예를 들어 stdio의 FILE 포인터 또는 libmysqlclient의 연결 핸들은 단순히 스칼라 값 배열에 매핑될 수 없습니다. 그렇지 않으면 사용자 공간 스크립트를 보호하기 위해 의미를 잃게 됩니다. 작성자는 이러한 문제를 처리할 필요가 없습니다. PHP는 범중국어 리소스 데이터 유형을 제공하므로 9장 "리소스 데이터 유형"에서 리소스 유형의 구현 세부 사항을 다룰 것입니다. . 알았어.


上表中的IS_*常量被存储在zval结构的type元素中, 用来确定在测试变量的值时应该查看value元素中的哪个部分.

最明显的检查一个数据的类型的方法如下代码:

void describe_zval(zval *foo)  
{  
    if (foo->type == IS_NULL) {  
        php_printf("The variable is NULL");  
    } else {  
        php_printf("The variable is of type %d", foo->type);  
    }  
}

显而易见, 但是是错的.

好吧, 没有错, 但确实不是首选做法. Zend头文件包含了很多的zval访问宏, 它们是作者期望在测试zval数据时使用的方式. 这样做主要的原因是避免在引擎的api变更后产生不兼容问题, 不过从另一方面来看这样做还会使得代码更加易读. 下面是相同功能的代码段, 这一次使用了Z_TYPE_P()宏:

void describe_zval(zval *foo)  
{  
    if (Z_TYPE_P(foo) == IS_NULL) {  
        php_printf("The variable is NULL");  
    } else {  
        php_printf("The variable is of type %d",  
                            Z_TYPE_P(foo));  
    }  
}

这个宏的_P后缀标识传递的参数应该是一级间访的指针. 还有另外两个宏Z_TYPE()和Z_TYPE_PP(), 它们期望的参数类型是zval(非指针)和zval **(两级间访指针).

注意

在这个例子中使用了一个特殊的输出函数php_printf(), 它被用于展示数据片. 这个函数语法上等同于stdio的printf函数; 不过它对webserver sapi有特殊的处理, 使用php的输出缓冲机制提升性能. 你将在第5章"你的第一个扩展"中更多的了解这个函数以及它的同族PHPWRITE().

数据值

和类型一样, zval的值也可以用3个一组的宏检查. 这些宏总是以Z_开始, 可选的以_P或_PP结尾, 具体依赖于它们的间访层级.

对于简单的标量类型, boolean, long, double, 宏简写为: BVAL, LVAL, DVAL.

void display_values(zval boolzv, zval *longpzv,  
                zval **doubleppzv)  
{  
    if (Z_TYPE(boolzv) == IS_BOOL) {  
        php_printf("The value of the boolean is: %s\n",  
            Z_BVAL(boolzv) ? "true" : "false");  
    }  
    if (Z_TYPE_P(longpzv) == IS_LONG) {  
        php_printf("The value of the long is: %ld\n",  
            Z_LVAL_P(longpzv));  
    }  
    if (Z_TYPE_PP(doubleppzv) == IS_DOUBLE) {  
        php_printf("The value of the double is: %f\n",  
            Z_DVAL_PP(doubleppzv));  
    }  
}

由于字符串变量包含两个成员, 因此它有一对宏分别表示char *(STRVAL)和int(STRLEN)成员:

void display_string(zval *zstr)  
{  
    if (Z_TYPE_P(zstr) != IS_STRING) {  
        php_printf("The wrong datatype was passed!\n");  
        return;  
    }  
    PHPWRITE(Z_STRVAL_P(zstr), Z_STRLEN_P(zstr));  
}

数组数据类型内部以HashTable *存储, 可以使用: Z_ARRVAL(zv), Z_ARRVAL_P(pzv), Z_ARRVAL_PP(ppzv)访问. 在阅读旧的php内核和pecl模块的代码时, 你可能会碰到HASH_OF()宏, 它期望一个zval *参数. 这个宏等价于Z_ARRVAL_P()宏, 不过, 这个用法已经废弃, 在新的代码中应该不再被使用.

对象的内部表示结构比较复杂, 它有较多的访问宏: OBJ_HANDLE返回处理标识, OBJ_HT返回处理器表, OBJCE用于类定义, OBJPROP用于属性的HahsTable, OBJ_HANDLER用于维护OBJ_HT表中的一个特殊处理器方法. 现在不要被这么多的对象访问宏吓到, 在第10章"php4对象"和第11章"php5对象"中它们的细节都会介绍.

在一个zval中, 资源数据类型被存储为一个简单的整型, 它可以通过RESVAL这一组宏来访问. 这个整型将被传递给zend_fetch_resource()函数在已注册资源列表中查找资源对象. 我们将在第9章深入讨论资源数据类型.

数据的创建

现在你知道了怎样从一个zval中取出数据, 是时候创建一些自己的数据了. 虽然zval可以作为一个直接变量定义在函数的顶部, 这使得变量的数据存储在本地, 为了让它离开这个函数到达用户空间就需要对其进行拷贝.

因为你大多数时候都是希望自己创建的zval到达用户空间, 因此你就需要分配一个块内存给它, 并且将它赋值给一个zval *指针. 与之前的"显而易见"的方案一样, 使用malloc(sizeof(zval))并不是正确的答案. 取而代之的是你要用另外一个Zend宏: MAKE_STD_ZVAL(pzv). 这个宏将会以一种优化的方式在其他zval附近为其分配内存, 自动的处理超出内存错误(下一章将会解释), 并初始化新zval的refcount和is_ref属性.

除了MAKE_STD_ZVAL(), 你可能还经常会碰到其他的zval *创建宏, 比如ALLOC_INIT_ZVAL(). 这个宏和MAKE_STD_ZVAL唯一的区别是它会将zval *的数据类型初始化为IS_NULL.

一旦数据存储空间可用, 就可以向你的新zval中填充一些信息了. 在阅读了前面的数据存储部分后, 你可能准备使用Z_TYPE_P()和Z_SOMEVAL_P()宏去设置你的新变量. 我们来看看这个"显而易见"的方案是否正确?

同样, "显而易见"的并不正确!

Zend暴露了另外一组宏用来设置zval *的值. 下面就是这些新的宏和它们展开后你已经熟悉的格式:

ZVAL_NULL(pvz);                   Z_TYPE_P(pzv) = IS_NULL;

虽然这些宏相比使用更加直接的版本并没有节省什么, 但它的出现体现了完整性.

ZVAL_BOOL(pzv, b);                Z_TYPE_P(pzv) = IS_BOOL;  
                                  Z_BVAL_P(pzv) = b ? 1 : 0;  
ZVAL_TRUE(pzv);                   ZVAL_BOOL(pzv, 1);  
ZVAL_FALSE(pzv);                  ZVAL_BOOL(pzv, 0);

注意, 任何非0值提供给ZVAL_BOOL()都将产生一个真值. 当在内部代码中硬编码时, 使用1表示真值被认为是较好的实践. 宏ZVAL_TRUE()和ZVAL_FALSE()提供用来方便编码, 有时也会提升代码的可读性.

ZVAL_LONG(pzv, l);                Z_TYPE_P(pzv) = IS_LONG;  
                                  Z_LVAL_P(pzv) = l;  
ZVAL_DOUBLE(pzv, d);              Z_TYPE_P(pzv) = IS_DOUBLE;  
                                  Z_DVAL_P(pzv) = d;

基础的标量宏和它们自己一样简单. 设置zval的类型, 并给它赋一个数值.

ZVAL_STRINGL(pzv,str,len,dup);    Z_TYPE_P(pzv) = IS_STRING;  
                                  Z_STRLEN_P(pzv) = len;  
                                  if (dup) {  
                                      Z_STRVAL_P(pzv) =  
                                            estrndup(str, len + 1);  
                                  } else {  
                                     Z_STRVAL_P(pzv) = str;  
                                  }  
ZVAL_STRING(pzv, str, dup);       ZVAL _STRINGL(pzv, str,  
                                                strlen(str), dup);

这里, zval的创建就开始变得有趣了. 字符串就像数组, 对象, 资源一样, 需要分配额外的内存用于它们的数据存储. 在下一章你将继续探索内存管理的陷阱; 现在, 只需要注意, 当dup的值为1时, 将分配新的内存并拷贝字符串内容, 当dup的值为0时, 只是简单的将zval指向已经存在的字符串数据.

ZVAL_RESOURCE(pzv, res);          Z_TYPE_P(pzv) = IS_RESOURCE;  
                                  Z_RESVAL_P(pzv) = res;

回顾前面, 资源在zval中只是存储了一个简单的整型, 它用于在Zend管理的资源表中查找. 因此ZVAL_RESOURCE()宏就很像ZVAL_LONG()宏, 但是, 使用不同的类型.

数据类型/值/创建回顾练习

static void eae_001_zval_dump_real(zval *z, int level) {  
    HashTable   *ht;  
    int         ret;  
    char        *key;  
    uint        index;  
    zval        **pData;  
  
    switch ( Z_TYPE_P(z) ) {  
        case IS_NULL:  
            php_printf("%*stype = null, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");  
            break;  
        case IS_BOOL:  
            php_printf("%*stype = bool, refcount = %d%s, value = %s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", 
            is_ref " : "", Z_BVAL_P(z) ? "true" : "false");  
            break;  
        case IS_LONG:  
            php_printf("%*stype = long, refcount = %d%s, value = %ld\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", 
            is_ref " : "", Z_LVAL_P(z));  
            break;  
        case IS_STRING:  
            php_printf("%*stype = string, refcount = %d%s, value = \"%s\", len = %d\n", level * 4, "", Z_REFCOUNT_P(z), 
            Z_ISREF_P(z) ? ", is_ref " : "", Z_STRVAL_P(z), Z_STRLEN_P(z));  
            break;  
        case IS_DOUBLE:  
            php_printf("%*stype = double, refcount = %d%s, value = %0.6f\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", 
            is_ref " : "", Z_DVAL_P(z));  
            break;  
        case IS_RESOURCE:  
            php_printf("%*stype = resource, refcount = %d%s, resource_id = %d\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", 
            is_ref " : "", Z_RESVAL_P(z));  
            break;  
        case IS_ARRAY:  
            ht      = Z_ARRVAL_P(z);  
  
            zend_hash_internal_pointer_reset(ht);  
            php_printf("%*stype = array, refcount = %d%s, value = %s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", 
            is_ref " : "", HASH_KEY_NON_EXISTANT != zend_hash_has_more_elements(ht) ? "" : "empty");  
            while ( HASH_KEY_NON_EXISTANT != (ret = zend_hash_get_current_key(ht, &key, &index, 0)) ) {  
                if ( HASH_KEY_IS_STRING == ret ) {  
                    php_printf("%*skey is string \"%s\"", (level + 1) * 4, "", key);  
                } else if ( HASH_KEY_IS_LONG == ret ) {  
                    php_printf("%*skey is long %d", (level + 1) * 4, "", index);  
                }  
                ret = zend_hash_get_current_data(ht, &pData);  
                eae_001_zval_dump_real(*pData, level + 1);  
                zend_hash_move_forward(ht);  
            }  
            zend_hash_internal_pointer_end(Z_ARRVAL_P(z));  
            break;  
        case IS_OBJECT:  
            php_printf("%*stype = object, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");  
            break;  
        default:  
            php_printf("%*sunknown type, refcount = %d%s\n", level * 4, "", Z_REFCOUNT_P(z), Z_ISREF_P(z) ? ", is_ref " : "");  
            break;  
    }  
}  
  
PHP_FUNCTION(eae_001_zval_dump)  
{  
    zval    *z;  
  
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &z) == FAILURE) {  
        return;  
    }  
  
    eae_001_zval_dump_real(z, 0);  
  
    RETURN_NULL();  
}  
  
PHP_FUNCTION(eae_001_zval_make)  
{  
    zval    *z;  
  
    MAKE_STD_ZVAL(z);  
  
    ZVAL_NULL(z);  
    eae_001_zval_dump_real(z, 0);  
  
    ZVAL_TRUE(z);  
    eae_001_zval_dump_real(z, 0);  
  
    ZVAL_FALSE(z);  
    eae_001_zval_dump_real(z, 0);  
  
    ZVAL_LONG(z, 100);  
    eae_001_zval_dump_real(z, 0);  
  
    ZVAL_DOUBLE(z, 100.0);  
    eae_001_zval_dump_real(z, 0);  
  
    ZVAL_STRING(z, "100", 0);  
    eae_001_zval_dump_real(z, 0);  
}

数据存储

你已经在用户空间一侧使用过php了, 因此你应该已经比较熟悉数组了. 我们可以将任意数量的php变量(zval)放入到一个容器(array)中, 并可以为它们指派数字或字符串格式的名字(标签----key)

如果不出意外, php脚本中的每个变量都应该可以在一个数组中找到. 当你创建变量时, 为它赋一个值, Zend把这个值放到被称为符号表的一个内部数组中.

有一个符号表定义了全局作用域, 它在请求启动后, 扩展的RINIT方法被调用之前初始化, 接着在脚本执行完成后, 后续的RSHUTDOWN方法被执行之前销毁.

当一个用户空间的函数或对象方法被调用时, 则分配一个新的符号表用于函数或方法的生命周期, 它被定义为激活的符号表. 如果当前脚本的执行不在函数或方法中, 则全局符号表被认为是激活的.

我们来看看globals结构的实现(在Zend/zend_globals.h中定义), 你会看到下面的两个元素定义:

struct _zend_execution_globals {  
    ...  
    HashTable symbol_table;  
    HashTable *active_symbol_table;  
    ...  
};

symbol_table, 使用EG(symbol_table)访问, 它永远都是全局变量作用域, 和用户空间的$GLOBALS变量相似, 用于对应于php脚本的全局作用域. 实际上, $GLOBALS变量的内部就是对EG(symbol_table)上的一层包装.

另外一个元素active_symbol_table, 它的访问方法类似: EG(active_symbol_table), 表示此刻激活的变量作用域.

这里有一个需要注意的关键点, EG(symbol_table), 它不像你在php和zend api下工作时将遇到的几乎所有其他HashTable, 它是一个直接变量. 几乎所有的函数在HashTable上操作时都期望一个间访的HashTable *作为参数. 因此, 你在使用时需要在EG(symbol_table)前加取地址符(&).

考虑下面的代码块, 它们的功能是等价的

/* php实现 */  
<?php $foo = &#39;bar&#39;; ?>  
  
/* C实现 */  
{  
    zval *fooval;  
  
    MAKE_STD_ZVAL(fooval);  
    ZVAL_STRING(fooval, "bar", 1);  
    ZEND_SET_SYMBOL(EG(active_symbol_table), "foo", fooval);  
}

首先, 使用MAKE_STD_ZVAL()分配一个新的zval, 它的值被初始化为字符串"bar". 接着是一个新的宏调用, 它的作用是将fooval这个zval增加到当前激活的符号表中, 设置的变量名为"foo". 因为此刻并没有用户空间函数被激活, 因此EG(active_symbol_table) == &EG(symbol_table), 最终的含义就是这个变量被存储到了全局作用域中.

数据取回

为了从用户空间取回一个变量, 你需要在符号表的存储中查找. 下面的代码段展示了使用zend_hash_find()函数达成这个目的:

{  
    zval **fooval;  
  
    if (zend_hash_find(EG(active_symbol_table),  
                       "foo", sizeof("foo"),  
                       (void**)&fooval) == SUCCESS) {  
        php_printf("Got the value of $foo!");  
    } else {  
        php_printf("$foo is not defined.");  
    }  
}

这个例子中有一点看起来有点奇怪. 为什么要把fooval定义为两级间访指针呢? 为什么sizeof()用于确定"foo"的长度呢? 为什么是&fooval? 哪一个被评估为zval ***, 转换为void **?如果你问了你自己所有上面3个问题, 请拍拍自己的后背.

首先, 要知道HashTable并不仅用于用户空间变量, 这一点很有价值. HashTable结构用途很广, 它被用在整个引擎中, 甚至它还能完美的存储非指针数据. HashTable的桶是定长的, 因此, 为了存储任意大小的数据, HashTable将分配一块内存用来放置被存储的数据. 对于变量而言, 被存储的是一个zval *, 因此HashTable的存储机制分配了一块足够保存一个指针的内存. HashTable的桶使用这个新的指针保存zval *的值, 因此在HashTable中被保存的是zval **. HashTable完全可以漂亮的存储一个完整的zval, 那为什么还要这样存储zval *呢? 具体原因我们将在下一章讨论.

在尝试取回数据的时候, HashTable仅知道有一个指针指向某个数据. 为了将指针弹出到调用函数的本地存储中, 调用函数自然就要取本地指针(变量)的地址, 结果就是一个未知类型的两级间访的指针变量(比如void **). 要知道你的未知类型在这里是zval *, 你可以看到把这种类型传递给zend_hash_find()时, 编译器会发现不同, 它知道是三级间访而不是两级. 这就是我们在前面加一个强制类型转换的目的, 用来抑制编译器的警告.

在前面的例子中使用sizeof()的原因是为了在"foo"常量用作变量的标签时包含它的终止NULL字节. 这里使用4的效果是等价的; 不过这比较危险, 因为对标签名的修改会影响它的长度, 现在这样做在标签名变更时比较容易查找需要修改的地方. (strlen("foo") + 1)也可以解决这个问题, 但是, 有些编译器并没有优化这一步, 结果产生的二进制文件最终执行时可能得到的是一个毫无意义的字符串长度, 拿它去循环可不是那么好玩的!

如果zend_hash_find()定位到了你要查找的项, 它就会将所请求数据第一次被增加到HashTable中时时分配的桶的指针地址弹出到所提供的指针(zend_hash_find()第4个参数)中, 同时返回一个SUCCESS整型常量. 如果zend_hash_find()不能定位到数据, 它就不会修改指针(zend_hash_find()第四个参数)而是返回整型常量FAILURE.

站在用户空间的角度看, 变量存储到符号表所返回的SUCCESS或FAILURE实际上就是变量是否已经设置(isset).

类型转换

现在你可以从符号表抓取变量, 那可能你就想对它们做些什么. 一种直接的事倍功半的方法是检查变量的类型, 并依赖类型执行特殊的动作. 就像下面代码中简单的switch语句就可以工作.

void display_zval(zval *value)  
{  
    switch (Z_TYPE_P(value)) {  
        case IS_NULL:  
            /* NULLs are echoed as nothing */  
            break;  
        case IS_BOOL:  
            if (Z_BVAL_P(value)) {  
                php_printf("1");  
            }  
            break;  
        case IS_LONG:  
            php_printf("%ld", Z_LVAL_P(value));  
            break;  
        case IS_DOUBLE:  
            php_printf("%f", Z_DVAL_P(value));  
            break;  
        case IS_STRING:  
            PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));  
            break;  
        case IS_RESOURCE:  
            php_printf("Resource #%ld", Z_RESVAL_P(value));  
            break;  
        case IS_ARRAY:  
            php_printf("Array");  
            break;  
        case IS_OBJECT:  
            php_printf("Object");  
            break;  
        default:  
            /* Should never happen in practice, 
             * but it&#39;s dangerous to make assumptions 
             */  
             php_printf("Unknown");  
             break;  
    }  
}

是的, 简单, 正确. 对比前面eb4bd62954e83d68558dfe15e6d196b2的例子, 不难猜想这种编码会使得代码不好管理. 幸运的是, 在脚本执行输出变量的行为时, 无论是扩展, 还是嵌入式环境, 引擎都使用了非常相似的里程. 使用Zend暴露的convert_to_*()函数族可以让这个例子变得很简单:

void display_zval(zval *value)  
{  
    convert_to_string(value);  
    PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));  
}

你可能会猜到, 有很多这样的函数用于转换到大多数数据类型. 值得注意的是convert_to_resource(), 它没有意义, 因为资源类型的定义旧是不能映射到真实用户空间表示的值.

如果你担心convert_to_string()调用对传递给函数的zval的值的修改不可逆, 那说明你很棒. 在真正的代码段中, 这是典型的坏主意, 当然, 引擎在输出变量时并不是这样做的. 下一章你将会看到安全的使用转换函数的方法, 它会安全的修改值的内容, 而不会破坏它已有的内容.

小结

本章中你看到了php变量的内部表示. 你学习了区别类型, 设置和取回值, 将变量增加到符号表中以及将它们取回. 下一章你将在这些知识的基础之上, 学习怎样拷贝一个zval, 怎样在不需要的时候销毁它们, 最重要的而是, 怎样避免在不需要的时候产生拷贝.

你还将看到Zend的单请求内存管理层的一角, 了解了持久化和非持久化分配. 在下一章的结尾, 你旧有实力可以去创建一个工作的扩展并在上面用自己的代码做实验了.

以上就是 [翻译][php扩展开发和嵌入式]第2章-变量的里里外外的内容,更多相关内容请关注PHP中文网(www.php.cn)!


유형 값 목적
IS_NULL 이 유형은 처음 사용될 때까지 초기화되지 않은 변수에 자동으로 할당됩니다. 명시적 할당을 위해 내장된 사용자 공간 NULL 상수를 사용할 수도 있습니다. 이 변수 ​​유형은 특별한 "데이터 없음" 유형을 제공합니다. 이는 부울 FALSE 및 정수 0과 다릅니다.
IS_BOOL 부울 변수는 if와 같은 사용자 공간 제어 구조의 두 가지 가능한 상태 중 하나를 가질 수 있습니다. /while/ternary/for 및 그 사이에 있는 다른 조건식은 부울 유형입니다.
IS_DOUBLE 부동 소수점 데이터 유형은 호스트 시스템의 부호 있는 이중 데이터 유형 부동 소수점 숫자는 정확한 정밀도로 저장되지 않습니다. 공식을 사용하여 값의 소수 부분의 유한 정밀도를 나타냅니다(주석: 부동 소수점 숫자는 부호, 가수 - 분수 부분, 지수. 부동 소수점 숫자의 값 = 부호 * 가수 * 2^지수 - BSD 라이브러리 함수 매뉴얼: float(3)) 이 표기법을 사용하면 컴퓨터가 다양한 값(양수 또는 음수): 2.225*10^(-308) ~ 1.798*10^(8바이트 308로 표시 가능) 불행히도 평가되는 숫자의 실제 십진수 값이 항상 이진 분수만큼 깔끔하게 저장되는 것은 아닙니다. , 십진수 표현식 0.5는 정확한 이진수 값 0.1로 변환되는 반면, 십진수 표현식 0.8은 이진수로 변환되는 것은 0.1100110011의 무한 루프입니다.... 십진수로 다시 변환하면 버려진 이진수 비트는 복구할 수 없습니다. 마찬가지로 1/3을 10진수 0.333333으로 변환한다고 생각할 수 있습니다. 두 값은 매우 유사하지만 3 * 0.333333은 1.0과 같지 않기 때문에 종종 혼동을 줍니다. (이러한 범위 제한은 일반적으로 32비트 플랫폼을 기반으로 하며 시스템 범위에 따라 다를 수 있습니다.)
IS_STRING

가장 일반적입니다. PHP의 데이터 유형은 문자열이며 숙련된 C 프로그래머가 예상하는 방식으로 저장됩니다. 블록을 할당하는 것으로 충분합니다. 문자열의 모든 바이트/문자의 메모리를 저장하고 호스트 zval의 문자열에 대한 포인터를 저장합니다.

Zval 구조에서는 PHP 문자열의 길이가 항상 명시적이므로 문자열이 잘리지 않고 NULL 바이트를 포함할 수 있다는 점에 주목할 필요가 있습니다. 이렇게 하면 모든 유형의 바이너리 데이터를 안전하게 포함할 수 있습니다.

PHP 문자열에 할당된 총 메모리 양은 항상 최소화됩니다. 길이에 1을 더한 것입니다. 마지막 바이트는 종료 NULL을 저장합니다. 문자이므로 바이너리 안전 함수는 문제가 되지 않습니다. 문자열 포인터를 직접 전달할 수 있습니다.

IS_ARRAY 배열은 C의 배열 개념과 달리 다른 변수를 구성하는 기능만 하는 특수 목적 변수입니다. 단일 유형의 데이터(예: zval arrayofzvals[];) 실제로 PHP의 배열은 데이터 버킷의 복잡한 모음이며 내부는 HashTable입니다. 각 HashTable 요소(버킷)에는 태그와 데이터라는 두 가지 해당 정보가 포함되어 있습니다. . PHP 배열의 응용 시나리오에서 태그는 연결된 배열의 키 또는 값입니다.
IS_OBJECT
성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.