Heim > Backend-Entwicklung > PHP-Tutorial > [Übersetzung][PHP-Erweiterungsentwicklung und eingebettet] Kapitel 2 – Das Innere und Äußere von Variablen

[Übersetzung][PHP-Erweiterungsentwicklung und eingebettet] Kapitel 2 – Das Innere und Äußere von Variablen

黄舟
Freigeben: 2023-03-05 16:08:02
Original
1428 Leute haben es durchsucht

Ins und Outs von Variablen

Eine gemeinsame Funktion aller Programmiersprachen ist das Speichern und Abrufen von Informationen; PHP ist jedoch keine Ausnahme, obwohl viele Sprachen erfordern, dass alle Variablen und ihre Typinformationen vor der Verwendung definiert werden ist behoben, aber PHP ermöglicht es Programmierern, Variablen zu erstellen, wenn sie diese verwenden, und kann Informationen speichern, die in jeder Art von Sprache ausgedrückt werden können. Es kann bei Bedarf auch automatisch Variablentypen konvertieren.

Weil Sie Benutzer- verwendet haben. Leerzeichen PHP, Sie sollten wissen, dass es sich bei diesem Konzept um „schwache Typisierung“ handelt. In diesem Kapitel sehen Sie diese Informationen in der übergeordneten Sprache von PHP (die Typen von C sind streng). Wie ist sie codiert? Das Kodieren der Daten ist nur die halbe Arbeit. Um den Überblick über all diese Informationen zu behalten, benötigt jede Variable auch eine Bezeichnung und einen Container. Aus Userspace-Perspektive kann man sie sich als die Konzepte von Variablennamen und -bereich vorstellen.

Datentyp

Die Datenspeichereinheit in PHP ist zval, auch Zend Value genannt. Es handelt sich um eine Struktur mit nur 4 Mitgliedern. Der Körper ist in Zend/zend.h definiert und hat das folgende Format:

Wir können die grundlegenden Speichertypen der meisten dieser Mitglieder erraten: refcount für vorzeichenlose Ganzzahlen, type und is_ref für vorzeichenlose Zeichen. Das Wertmitglied ist tatsächlich eine Struktur, die in PHP5 als Union definiert ist ist wie folgt definiert:
typedef struct _zval_struct {  
    zval_value  value;  
    zend_uint   refcount;  
    zend_uchar  type;  
    zend_uchar  is_ref;  
} zval;
Nach dem Login kopieren

Union ermöglicht Zend die Verwendung einer einzigen, einheitlichen Struktur zum Speichern vieler verschiedener Datentypen in einer PHP-Variable
typedef union _zvalue_value {  
    long                    lval;  
    double              dval;  
    struct {  
        char        *val;  
        int     len;  
    }                   str;  
    HashTable           *ht;  
    zend_object_value       obj;  
} zvalue_value;
Nach dem Login kopieren

Zend definiert derzeit 8 aufgelistete Datentypen in der folgenden Tabelle:


geben Sie Wert ein IS_NULLIS_BOOLIS_DOUBLEIS_STRINGDer häufigste Datentyp in PHP ist String, der so gespeichert wird, wie es erfahrene C-Programmierer erwarten würden. Weisen Sie einen Speicher zu, der groß genug ist, um alle Bytes/Zeichen im String aufzunehmen, und speichern Sie den Zeiger darauf die Zeichenfolge im Host-zval.
Zweck
Dieser Typ wird nicht initialisierten Variablen automatisch zugewiesen, bis er zum ersten Mal verwendet wird. Er kann auch vom Benutzer verwendet werden. Der Raum verwendet die integrierte NULL-Konstante für die explizite Zuweisung. Dieser Variablentyp bietet eine spezielle „Nr.“ Datentyp, der sich vom booleschen FALSE und der Ganzzahl 0 unterscheidet.
Boolesche Variablen können einen von zwei möglichen Zuständen haben, TRUE/FALSE. Benutzer Raumkontrollstrukturen if/while/ternary/for und andere bedingte Ausdrücke dazwischen werden bei der Auswertung der impliziten Konvertierung in den booleschen Typ ausgewertet.
Gleitkomma-Datentyp Mithilfe des vorzeichenbehafteten Doppeldatentyps des Hostsystems werden Gleitkommazahlen nicht mit exakter Genauigkeit gespeichert. Stattdessen wird eine Formel verwendet, um die endliche Genauigkeit des Dezimalteils des Werts darzustellen (Anmerkung: Gleitkommazahlen werden als 3 Teile dargestellt : Vorzeichen, Mantisse - Bruchteil, Exponent. Der Wert einer Gleitkommazahl = Vorzeichen * Mantisse * 2^Exponent - --Aus dem BSD Library Functions Manual: float(3)). Wertebereich (positiv oder negativ): 2,225*10^(-308) bis 1,798 kann in 8 Bytes dargestellt werden. *Eine Zahl im Bereich *10^(308) ist leider die tatsächliche Dezimalzahl der Zahl, zu der sie ausgewertet wird wird nicht immer so sauber gespeichert wie ein binärer Bruch. Beispielsweise wird der Dezimalausdruck 0,5 in den exakten Binärwert 0,1 konvertiert, während die Konvertierung von 0,8 in den Binärwert eine Endlosschleife von 0,1100110011... ist Zurück in eine Dezimalzahl umgewandelt, können die verworfenen Binärbits nicht wiederhergestellt werden, da sie nicht gespeichert werden können. Ebenso können Sie sich vorstellen, 1/3 in eine Dezimalzahl von 0,333333 umzuwandeln. Der Wert ist sehr ähnlich, aber ungenau, da 3 * 0,333333 nicht gleich ist Diese Ungenauigkeit verwirrt Menschen oft bei der Verarbeitung von Gleitkommazahlen auf Computern (diese Bereichsgrenzen basieren normalerweise auf 32-Bit-Plattformen; verschiedene Systeme können variieren)

Es ist zu beachten, dass die Länge der PHP-Zeichenfolge immer explizit in der zval-Struktur angegeben wird. Dadurch kann die Zeichenfolge NULL-Bytes enthalten, ohne dass sie abgeschnitten wird Aspekt von PHP-Strings „binärsicher“, da dadurch jede Art von Binärdaten sicher enthalten werden kann.

Es ist zu beachten, dass die Gesamtspeichermenge, die einem PHP-String zugewiesen wird, immer minimiert wird: die Länge plus 1. Das letzte Byte speichert das abschließende NULL-Zeichen, sodass es keine Rolle spielt. Binärsichere Funktionen können direkt an String-Zeiger übergeben werden.

IS_ARRAYEin Array ist eine Spezialvariable, deren einzige Funktion darin besteht, andere Variablen zu organisieren. Im Gegensatz zum Array-Konzept in C ist das Array von PHP kein Vektor von a Einzelner Datentyp (z. B. zval arrayofzvals[];). Tatsächlich ist das Array von PHP eine komplexe Sammlung von Daten-Buckets und sein Inneres ist eine HashTable. Jedes HashTable-Element (Bucket) enthält zwei entsprechende Informationen: Tags und Daten . Im Anwendungsszenario des PHP-Arrays ist das Tag der Schlüssel oder Wert des zugehörigen Arrays. Die Daten sind die Variable (zval), auf die der Schlüssel zeigt ><🎜 Objekte verfügen zusätzlich zum Hinzufügen von Methoden, Zugriffsmodifikatoren, Bereichskonstanten und speziellen Ereignishandlern über Arrays zur Datenspeicherung mit mehreren Elementen. Als Erweiterungsentwickler ist es eine große Herausforderung, äquivalenten objektorientierten Code in PHP4 zu erstellen php5. Eine große Herausforderung, da es sehr große Änderungen im internen Objektmodell zwischen Zend Engine 1 (php4) und Zend Engine 2 (php5) gibt.
IS_RESOURCEEs gibt einige Datentypen, die nicht einfach dem Benutzerbereich zugeordnet werden können, zum Beispiel der FILE-Zeiger von libmysqlclient. Sie können nicht einfach einem Array von Skalarwerten zugeordnet werden, da sie sonst ihre Bedeutung verlieren Mit diesen Problemen müssen sich Space-Skript-Autoren nicht befassen. Die Implementierungsdetails der Ressourcentypen werden wir nun in Kapitel 9 „Ressourcendatentypen“ behandeln eine Sache. Okay.


上表中的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);  
    }  
}
Nach dem Login kopieren

显而易见, 但是是错的.

好吧, 没有错, 但确实不是首选做法. 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));  
    }  
}
Nach dem Login kopieren

这个宏的_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));  
    }  
}
Nach dem Login kopieren

由于字符串变量包含两个成员, 因此它有一对宏分别表示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));  
}
Nach dem Login kopieren

数组数据类型内部以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;
Nach dem Login kopieren

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

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);
Nach dem Login kopieren

注意, 任何非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;
Nach dem Login kopieren

基础的标量宏和它们自己一样简单. 设置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);
Nach dem Login kopieren

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

ZVAL_RESOURCE(pzv, res);          Z_TYPE_P(pzv) = IS_RESOURCE;  
                                  Z_RESVAL_P(pzv) = res;
Nach dem Login kopieren

回顾前面, 资源在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);  
}
Nach dem Login kopieren

数据存储

你已经在用户空间一侧使用过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;  
    ...  
};
Nach dem Login kopieren

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);  
}
Nach dem Login kopieren

首先, 使用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.");  
    }  
}
Nach dem Login kopieren

这个例子中有一点看起来有点奇怪. 为什么要把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;  
    }  
}
Nach dem Login kopieren

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

void display_zval(zval *value)  
{  
    convert_to_string(value);  
    PHPWRITE(Z_STRVAL_P(value), Z_STRLEN_P(value));  
}
Nach dem Login kopieren

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

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

小结

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

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

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


Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage