Christian Seiler: Interna von Variablen und Ressourcen, Lebensdauer, GC

Beitrag lesen

Hallo,

also, mal ein kleiner Exkurs zum Thema "Ressourcen in PHP - wie funktionieren die?":

(Disclaimer: Ich betrachte hier PHP 5.3, PHP 5.2 ist aber bis auf den zykluserkennenden GC für die für dieses Posting relevante Aspekte identisch im Verhalten.)

(Disclaimer 2: Ein besserer Titel für dieses Posting wäre wohl gewesen: Mehr über die Interna von PHP, als das gesammelte SELFHTML-Forum je wissen wollte.)

(Disclaimer 3: Nur weiterlesen, wenn man an sowas wirklich interessiert ist.)

Variablen in PHP

Zuerst einmal sollten wir uns ansehen, wie PHP Variablen intern (in C) speichert. Dazu gibt es eine Struktur "zval", die sieht so aus:

zend.h, ~ Zeile 328:

struct _zval_struct {
	/* Variable information */
	zvalue_value value;		/* value */
	zend_uint refcount__gc;
	zend_uchar type;	/* active type */
	zend_uchar is_ref__gc;
};

Die einzelnen Einträge:

- value: Der "Wert" der Variablen (siehe unten)  - refcount__gc: Wie oft die Speicherstruktur der Variblen referenziert                  wird (siehe unten zu "Referenzen")  - type: Der Typ der Variable  - is_ref__gc: Ob die Variable eine richtige Referenz ist (siehe unten)

Betrachten wir erstmal nur die wichtigsten Einträge, value und type. type gibt an, welchen Typ eine Variable hat. Dafür sind in PHP einige Konstanten definiert, die den Typ definieren:

zend.h, ~ Zeile 518:

#define IS_NULL         0
#define IS_LONG         1
#define IS_DOUBLE       2
#define IS_BOOL         3
#define IS_ARRAY        4
#define IS_OBJECT       5
#define IS_STRING       6
#define IS_RESOURCE     7

Es gibt ferner noch einige weitere Typen, die für interne Optimierungszwecke vorhanden sind:

#define IS_CONSTANT     8
#define IS_CONSTANT_ARRAY    9

Sowie einige Konstanten, die per Bitmaske zum Typ hinzuaddiert werden können, die keinerlei Relevanz für den eigentlichen Datentyp haben, aber für andere Dinge gut sind (wieder Interna der Engine):

#define IS_CONSTANT_TYPE_MASK   0x0f
#define IS_CONSTANT_UNQUALIFIED 0x10
#define IS_CONSTANT_INDEX       0x80
#define IS_LEXICAL_VAR          0x20
#define IS_LEXICAL_REF          0x40

Wir ignorieren mal die erweiterten Dinge (alles > 7), die sind für die Diskussion nicht relevant, ich hab sie nur der Vollständigkeit halber angeführt. Die Basis-Typen sind relativ klar:

Konstante      PHP-Datentyp  ---------------------------------  IS_NULL        null  IS_LONG        int  IS_DOUBLE      float  IS_BOOL        boolean  IS_ARRAY       array  IS_OBJECT      object  IS_STRING      string  IS_RESOURCE    resource

[Anm.: In PHP 6 kommt natürlich noch IS_UNICODE für den unicode-Datentyp dazu.]

Nun betrachten wir das Element value der Datenstruktur. Das ist vom Typ zvalue_value - und das ist eine Union:

zend.h, ~ Zeile 317:

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

Diese Union enthält folgende Elemente - je nach Datentyp:

Datentyp  relevantes Element   -------------------------------   null      keines   int       lval (enthält den Integer-Wert direkt)   float     dval (enthält den Double-Wert direkt)   boolean   lval (ist entweder 0 oder 1)   array     ht   (HashTable ist eine C-Struktur mit der PHP Arrays                   abbildet, ich gehe hier nicht näher darauf ein)   object    obj  (zend_object_value ist ein bisschen komplizierter, im                   Endeffekt ist das wichtigste daran aber eine Zahl)   string    str  (in str.val wird der String als 0-Terminierte Zeichenkette                   (8-bit) abgelegt, in len wird zusätzlich die Länge                   gespeichert, damit PHP binäre Daten behalten kann)   resource  lval (enthält eine Zahl, die mit der Ressource zu tun hat                   und bei var_dump angezeigt wird)

Speichermodell von Ressourcen

Nun, wie wir hier sehen, werden Ressourcen als Zahlen gespeichert - wo sind aber die eigentlichen Daten der Ressource?

Nunja, das ist etwas komplizierter. Es gibt in PHP eine Struktur, die nennt sich executor_globals (Typ ist definiert in zend_globals.h, ~ Zeile 164, sehr lang) - und dieses enthält zwei Einträge:

struct _zend_executor_globals {
// ...
	HashTable regular_list;
	HashTable persistent_list;
// ...
};

Wir erinnern uns: HashTable ist PHPs interne Datenstruktur, um Arrays zu speichern. Sie kann alles, was normale Arrays in PHP auch können. Hier wird sie verwendet, um numerisch indizierte Daten zu speichern, die Nummer ist gerade die Nummer der Ressource. Der Unterschied zwischen regular_list und persistent_list hat nur zu tun mit den Funktionen, die persistente Verbindungen zu einer Datenbank aufbauen können, mysql_pconnect und so weiter. Die ignorieren wir hier mal und betrachten normale Ressourcen, wie die meisten es sind (File-Handles, Datenbank-Resultsets [1], etc.) und vor allem die sind, die uns interessieren. Die werden in dem Element regular_list gespeichert.

Was für einen Typ haben denn die Elemente, die in dieser HashTable gespeichert werden (das sieht man so einem Ding nicht an - bei normalen PHP-Arrays sind die Elemente wieder zval-Strukturen bzw. genauer gesagt Zeiger auf diese)?

zend_list.h, ~ Zeile 33:

typedef struct _zend_rsrc_list_entry {
	void *ptr;
	int type;
	int refcount;
} zend_rsrc_list_entry;

Betrachten wir die Elemente dieser Struktur:

ptr - Ein beliebiger Zeiger auf irgend etwas. Dies ist spezifisch für          die Erweiterung die die Ressource erstellt hat, es könnte z.B.          irgend eine MySQL-interne Struktur sein oder irgend etwas          ähnliches.    type - Der Typ der Ressource - damit wird verhindert, dass man File-           Handles an Datenbankfunktionen übergeben kann.    refcount - Wie oft diese konkrete Ressource referenziert wurde.

Betrachten wir zuerst Typen von Ressourcen: Jede Erweiterung, die eigene Ressourcen benutzen möchte, muss zuerst einen Ressourcen-Typ bei PHP registrieren, dies geht über die Funktion zend_register_list_destructors. Der übergibt man eine Desktruktor-Funktion [2] und eine interne Kennung von welcher Erweiterung die Ressource registriert wurde. Die Desktruktor-Funktion ist eine Funktion die man bereit stellen muss, die sich darum kümmert, dass eine Ressource ordnungsgemäß aufgeräumt wird. Die wird aufgerufen, sobald eine Ressource freigegeben wird. zend_register_list_destructors gibt der Erweiterung dann den zugewiesenen Typ zurück - das war ja eine Zahl. Der Typ kann dann in der obigen Struktur als Mitglied type gespeichert werden.

Anlegen von Ressourcen (veranschaulicht)

Stellen wir uns nun vor, eine Erweiterung möchte in PHP eine Ressource in einer Variable speichern. Was macht sie dann?

Folgende Dinge seien gegeben:

* Der Typ der Ressource ist in der C-Variable le_example_resource_type    gespeichert.  * zval *myvar zeigt bereits auf einen gültigen Speicherbereich, der eine    frischinitialisierte zval-Struktur enthält.  * Die interne Struktur, zu der ich eine Ressource anlegen will, liege in    der Variable my_type *my_struct.

Die C-Erweiterung ruft nun folgendes auf:

ret = zend_register_resource (myvar, my_struct, le_example_resource_type);
if (ret == FAILURE) {
  // Fehlerbehandlung
}

Was macht nun ihrerseits zend_register_resource? Sie ruft zend_list_insert auf und falls das erfolgreich war wird die ID der neu zugewiesenen Ressource in der zval-Struktur abgelegt.

Was macht zend_list_insert? Der Prototyp ist so definiert:

int zend_list_insert(void *ptr, int type);

Die macht folgendes:

1. Eine neue Struktur des Types _zend_rsrc_list_entry wird angelegt,     mit den folgenden Feldern:

	le.ptr=ptr;
	le.type=type;
	le.refcount=1;

2. Die Struktur wird intern in den HashTable regular_list der     executor_globals-Struktur hinzugefügt, analog zu array_push().

3. Der frisch erstellte Index wird zurückgegeben.

Der Index wird dann wie bereits erwähnt von zend_register_resource der zval-Struktur als value.lval zugewiesen.

Kopien auf Ressourcen

Nun betrachten wir ausschließlich* erst einmal vollwertige Kopien von Ressourcen, d.h. die Frage, was denn genau passiert, wenn ich folgendes mache:

$ressource_kopie = $ressource;

(Hinweis: Ich ignoriere copy-on-write-Semantiken mal, denn es gibt wirklich Situationen, in der in der PHP-Engine eine echte Kopie auch ausgeführt wird.)

Wenn die obige Zeile ausgeführt wird, wird der Inhalt der Speicherstruktur der alten Variable ($ressource) in die Speicherstruktur der neuen Variable ($ressource_kopie) kopiert und dann über einige Umwege die Funktion _zval_copy_ctor_func auf die Speicherstruktur der neuen Variable ausgeführt. Die macht folgendes im Falle einer Ressourcen (zvalue zeigt auf die Speicherstruktur der neuen Variable):

zend_variables.c, ~ Zeile 110: zend_list_addref(zvalue->value.lval);

Wir erinnern uns: zvalue->value.lval ist die numerische Id der Ressource. Nun betrachten wir was zend_list_addref macht.

zend_list_addref nutzt die internen Funktionen für HashTables, die PHP zur Verfügung stellt, um die Ressourcenstruktur zu finden, die zu dieser numerischen Id gehört. Sobald diese gefunden wurde, wird der Eintrag refcount inkrementiert:

zend_list.c, ~ Zeile 87: le->refcount++;

Je mehr "Kopien" von der Ressource herumfliegen, desto höher wird die Zahl. Die eigentliche Ressource wird dagegen nicht kopiert, die Id ist daher eine Art Referenz.

Ausflug: Referenzen auf Variablen

PHP unterstützt allerdings auch noch ein weiteres Konzept: Referenzen. Man kann nämlich eine Variable zu einer Referenz einer anderen Variable machen - wenn dann eine von beiden geändert wird, werden alle Referenzen auch geändert.

Intern geschieht dies indem auf die gleiche C-Struktur gezeigt wird. Allerdings reicht dies noch nicht ganz: Man muss nämlich noch Buch führen, wie oft auf die Speicherstruktur tatsächlich gezeigt wird, damit man weiß, wann der Speicher freigegeben werden kann.

Also gibt es in jeder Speicherstruktur den Eintrag refcount__gc und is_ref__gc. is_ref__gc ist entweder 0 (keine Referenz) oder 1 (Referenz) und refcount__gc gibt an, wie oft die Variable referenziert wurde.

Beispiel (für einfache Variablen):

$a = 5; // refcount__gc($a) == 1
                       // is_ref__gc($a) == 0
$b =& $a; // $a und $b zeigen auf die gleiche C-Speicherstruktur
          // refcount__gc($a) == 2
          // is_ref__gc($a) == 1
$a = 3;  // $b wird auch geändert
$c =& $b; // $c zeigt auf gleiche Speicherstruktur wir $a und %b,
          // refcount__gc($a) == 3
          // is_ref__gc($a) == 1
// etc.

copy-on-write-Semantiken

Naja, und weil das ganze so schön war, setzen wir nun nochmal eins drauf. Aus Performancegründen kopiert PHP nämlich Variablen nur dann, wenn tatsächlich irgend etwas geändert wird. Beispiel:

$a = 'sehr langer string ...'; // refcount__gc($a) == 1
                                              // is_ref__gc($a) == 0
$b = $a; // $b zeigt nun auf die gleiche Speicherstelle wie $a
         // refcount__gc($a) == 2
         // is_ref__gc($a) == 0
$c = $b; // $c zeigt nun auf $a bzw. $b
         // refcount__gc($a) == 3
         // is_ref__gc($a) == 0
$a = 'neuer string'; // bei $b / $c wird der refcount__gc dekrementiert
                     // und bei $a wird eine neue Variablenstruktur
                     // angelegt, so dass
                     // refcount_gc($a) == 1, is_ref__gc($a) == 0
                     // refcount_gc($b) == 2, is_ref__gc($b) == 0
// usw.

Man erhält also eine Art Pseudo-Referenzen, bei denen jedoch beim Zuweisen die zugewiesene Variable "separiert" wird - d.h. die geänderte Variable erhält eine neue Struktur während der Rest der Variablen, die auf die gleiche Struktur gezeigt haben, unverändert geblieben sind - mit Ausnahme dass der refcount__gc kleiner geworden ist.

Das ganze gewinnt dann natürlich noch ein paar Nettigkeiten hinzu: Da diese beiden Arten von Referenzen inkompatibel miteinander sind (is_ref__gc kann ja nicht gleichzeitig 0 oder 1 sein), wird das Zusammenspiel gerne mal ziemlich kompliziert:

function test1 ($parameter) { return; }
function test2 (&$referenz_parameter) { return; }

$a = 5;
$b =& $a;
$c = $b; // Hier muss nun eine tatsächliche Kopie angelegt werden, da wir
         // ja nicht wollen, dass $c mit in der Referenz enthalten ist
         // $c soll stattdessen
test1 ($b); // Hier muss auch eine Kopie angelegt werden, weil wir ja auch
            // nicht wollen, dass die Funktion intern durch Änderung der
            // Parametervariable die übergebene Variable ändert
            // Dies ist übrigens der Hauptgrund, warum Referenzen in PHP
            // in Wirklichkeit sogar langsamer sind als "Kopien", weil
            // "Kopien" eben keine Kopien sind, Referenzen bei Readonly-
            // Funktionsparametern aber immer kopiert werden müssen
            // (Ja, das ist irgendwie pervers)
test2 ($b); // Hier kann solange das als Parameter übergeben wird der
            // refcount__gc einfach um 1 erhöht werden, es muss also keine
            // Kopie gemacht werden.

Noch lustiger wird's dann natürlich, wenn man den Inhalt einer Variable, die bereits eine Referenz oder eine Pseudo-Referenz ist, an eine andere Variable zuweist, die eine richtige Referenz wird. Dann drehen sich einem die Gehirnwindungen um, welche Variable jetzt nun wie eine neue Speicherstruktur bekommt und welche refcount__gc-Werte wie gesetzt werden. Vor allem wenn man dann in C mit Zeigern auf Zeigern arbeiten muss. ;-)

Wie auch immer, das wichtigste dieses Abschnitts ist hoffe ich hängen geblieben.

copy-on-write in Bezug auf Ressourcen

Nun haben wir folgendes gesehen: Ressourcen sind einerseits selbst nur Referenzen und die Ressource-Struktur hat einen Eintrag refcount, der angibt, wie viele "Kopien" der Ressource in verschiedenen (!) Variablenstrukturen umherschwirren. Außerdem kann die gleiche Variablenstruktur von mehreren Variablen geteilt werden und hat damit selbst noch einen Eintrag refcount__gc, der die Kopien der Variablenstruktur zählt.

Es ist also möglich, folgende Speichersituation zu konstruieren:

Variable               C-Struktur                           Ressource

+-------------------+                +---------------+   $a   ------------->| refcount__gc == 2 |   +------> 42: | refcount == 2 |   $b   ------------->| value.lval == 42--|---+            | type == 54    |                      | ...               |   |            | ptr == ...    |                      +-------------------+   |            +---------------+                                              |                      +-------------------+   |            +---------------+   $c   ------------->| refcount__gc == 1 |   |  +---> 36: | refcount == 1 |                      | value.lval == 42--|---+  |         | type == 56    |                      | ...               |      |         | ptr == ...    |                      +-------------------+      |         +---------------+                                                 |                      +-------------------+      |   $d   ------------->| refcount__gc == 1 |      |                      | value.lval == 36--|------+                      | ...               |                      +-------------------+

Schön, was? ;-)

Vernichten von Variablen, Garbage Collection 5.2 Edition

Nun, was passiert nun mit Variablen, die aus dem Funktionsscope fallen? Beispiel:

function foo () {
  $a = 5;
}

Was passiert mit $a am Ende der Funktion? Nun, da müssen wir in der Funktion zend_call_function nachsehen, was nach einem Funktionaufruf geschieht:

zend_execute_API.c, ~ Zeile 939 [3]: zend_hash_destroy(EG(active_symbol_table));

(EG(v) ist ein Macro, das zu executor_globals.v expandiert oder zu einem komplizierteren Ausdruck wenn Threads verwendet werden.)

zend_hash_destroy schaut im HashTable nach, welche Destrukturfunktion es aufrufen soll (die wird intern gespeichert) - und für EG(active_symbol_table) ist dies die Funktion zval_ptr_dtor.

Was macht nun zval_ptr_dtor? Schauen wir nach:

zend_execute_API.c, ~ Zeile 429: Z_DELREF_PP(zval_ptr);

Z_DELREF_PP ist ein Makro, das refcount__gc herabsetzt (es hat seine Gründe warum das ein Makro ist, die ich hier jetzt nicht näher ausführen will).

Was passiert nun als nächstes? (nächste Zeile)

if (Z_REFCOUNT_PP(zval_ptr) == 0) {

Also, wenn festgestellt wird, dass der Referenz-Zähler auf 0 angekommen ist, wird die Struktur nun nicht mehr benötigt. Dann wird im Endeffekt folgendes ausgeführt (mit ein paar Checks + kompliziertereren Dingen):

zval_dtor(*zval_ptr);

Schauen wir uns nun an, was zval_dtor() macht, wenn es sich um eine Ressource handelt:

zend_variables.c, ~ Zeile 60: zend_list_delete(zvalue->value.lval);

Was passiert nun in zend_list_delete?

zend_list.c, ~ Zeile 57:

		if (--le->refcount<=0) {
			return zend_hash_index_del(&EG(regular_list), id);
		} else {
			return SUCCESS;
		}

Wir sehen hier also, dass der Eintrag refcount der Ressource dekrementiert wird. Wenn dieser 0 erreicht hat (if-Abfrage), dann wird die Funktion zend_hash_index_del aufgerufen - die dann den Ressourcen-Eintrag komplett aus der Liste entfernt. Diese ruft dann wiederum über einige indirekte Dinge den Destruktor der Ressource auf (der ganz am Anfang per zend_register_list_destructors registriert wurde!) - und wenn der richtig geschrieben wurde (was m.W. für alle Standard-PHP-Erweiterungen der Fall ist), dann gibt er alles frei, worauf die Ressource vorher gezeigt hat.

Betrachten wir nun, was am Ende der folgenden Funktion geschieht:

function foobar () {
 $fp = fopen ('test.txt', 'r');
}

- Variable $fp gerät aus dem Scope -> refcount__gc der zugehörigen    Speicherstruktur wird dekrementiert.

- refcount__gc erreicht 0 -> Variablenstruktur wird aufgeräumt per    zval_dtor.

- zval_dtor für Ressourcen: Ruft zend_list_delete auf.

- zend_list_delete dekrementiert den refcount der Ressource.

- refcount der Ressource erreicht 0 -> Ressource-Destruktor wird    aufgerufen.

- Ressource-Destruktor schließt (hier in diesem Fall) die Datei.    Unmittelbar.

Damit werden Ressourcen immer automatisch am Ende einer Funktion freigegeben GENAU DANN WENN keine einzige Kopie/Referenz auf die ursprünglich durch diese Ressource angelegte Variable mehr existiert.

q.e.d.

Zusatz: Garbage Collection 5.3 Edition

Nunja, und weil's so schön war, können wir kurz noch über die Verbesserung des Garbage-Collectors in PHP 5.3 reden. Der ändert das oben beschriebene Verhalten nämlich NICHT. Das bleibt von PHP 5.2 zu PHP 5.3 gleich.

Allerdings kann der Garbage-Collector von PHP 5.3 etwas, was PHP 5.2 nicht konnte: Zyklische Referenzen auflösen. Machen wir mal ein Beispiel aus der OOP:

class Node {
  public $parent = null;
  public $children = array ();

  public function addChild (Node $n) {
    // Jaja, hier sollte eigentlich noch überprüft werden, ob $n->parent
    // null ist und dann reagiert werden, ich lasse das mal weg. ;-)
    $n->parent = $this;
    $this->children[] = $n;
  }
}

$root = new Node;
$root->addChild (new Node);
unset ($root);

Erst einmal: Was genau passiert hier? Das $n->parent = $this erzeugt eine "Referenz" auf das aktuelle Objekt (ich unterschlage hier mal sehr viele Details weil ich mit dem Posting fertig werden will ;-)). Das heißt, dass ich folgende logische Struktur habe:

$root       |       |              +-----------------------------------------+       |              |                                         |       v              v                                         |   +----------------------+           +----------------------+  |   | $parent = null       |  +------->| $parent = -----------|--+   | $children = array (  |  |        | $children = array () |   |    0 =>  ------------|--+        +----------------------+   | )                    |   +----------------------+

Wir sehen also: refcount__gc von $root ist 2 (oder der ->parent-Eintrag ist ne Kopie der Variablenstruktur und die interne Objekt-Id hat einen Objekt-refcount von 2, was aber hier für das Ergebnis irrelevant ist). Wenn man nun also mit unset ($root); die Variable explizit löscht (ans Ende der Funktion gelangen hat den gleichen Effekt), wird der refcount__gc um 1 dekrementiert. Der ist jetzt nur noch 1. Das ganze sieht dann so aus:

+-----------------------------------------+                      |                                         |                      v                                         |   +----------------------+           +----------------------+  |   | $parent = null       |  +------->| $parent = -----------|--+   | $children = array (  |  |        | $children = array () |   |    0 =>  ------------|--+        +----------------------+   | )                    |   +----------------------+

Die beiden Strukturen zeigen nun aufeinander - aber es gibt keine Möglichkeit, über simples Reference Counting die Dinge noch loszuwerden. Klar, am Ende des Requests sind die zwangsweise weg, weil PHP alle Variablen vernichtet, aber wenn man etwas längeres laufen hat oder etwas was dauernd zyklische Referenzen erzeugt und die zugehörigen Objekte (oder auch Arrays mit =&-Typ-Referenzen) wieder löscht ohne die Zyklen aufzubrechen, der hat sich ein Speicherleck gebaut - oftmals ein sehr großes.

Daher kann der neue Garbage-Collector in PHP 5.3 zyklische Referenzen erkennen und beseitigen - und somit zusätzlich zum bisherigen Verhalten Probleme verhindern, die sonst aufgetreten wären.

Abschluss

Ich hoffe, ich habe hiermit darlegen können, dass mein voriges Posting den Tatsachen entsprach und dass PHP tatsächlich hinter Ressourcen herräumt, die man im Scope rumliegen lässt, sobald man ebendiesen verlässt.

Ich hoffe ferner auch, dass Aussagen meinerseits zu den Interna von PHP nicht mehr nur deswegen abgetan werden, weil sie nicht in das bereits vorhandene, mehr oder weniger verzerrte Weltbild passen, sondern erst dann, wenn objektive (!) Indizien vorhanden sind, dass sie falsch sind. Nein, ich will kein Autoritätsargument à la "Ich hab's gesagt, also stimmt's." konstruieren - aber bei Experten auf einem gewissen Gebiet sollte IMHO doch gelten: "Ich hab's gesagt, also sollte man das genauer untersuchen, bevor man ihm doch widerspricht." Ich kann mich selbstverständlich auch irren, keine Frage, aber meine Erfahrung auf einem gewissen Gebiet (hier: PHP-Interna) sollte als sehr starkes Argument für den Wahrheitsgehalt der Aussage gelten, das erst mit anderen Argumenten aufgewogen werden muss. Ich hoffe, das kommt jetzt nicht zu arrogant herüber, es ist einfach nur eine praktische Überlegung: Wenn jeder Experte jedem gegenüber jede Kleinigkeit immer wieder von vorne rechtfertigen müsste, ohne dass der andere ausreichend Anstrengungen unternimmt, dies vorab (!) selbst zu verstehen, dann stünde die Welt still.

Fußnoten

[1] Die Resultsets von persistenten Verbindungen sind selbst nicht persistent.

[2] Eigentlich sind es zwei Destruktor-Funktionen: Je eine für persistente und normale Ressourcen.

[3] Ok, in Wirklichkeit ist's etwas komplizierter, da HashTables aus Performancegründen gecached werden (siehe else-Zweig an der Stelle), allerdings wird im else-Zweig dennoch zend_hash_clean() aufgerufen, was den Hash komplett leert und damit auch die Destruktoren aufruft.

Viele Grüße, Christian

PS: Für alle, die sich wundern: Ja, ich habe ganz schamlos meine Privilegien auf dem Server ausgenutzt, um das Limit für Postinggrößen temporär zu erhöhen, damit ich das hier nicht herunterbrechen musste. ;-)

--
Mein "Weblog" [RSS] Using XSLT to create JSON output (Saxon-B 9.0 for Java) »I don't believe you can call yourself a web developer until you've built an app that uses hyperlinks for deletion and have all your data deleted by a search bot.«             -- Kommentar bei TDWTF
0 55

Datumrechnung

Simone
  • php
  1. 0
    EKKi
    1. 0
      Simone
      1. 0
        Christoph Jeschke
        1. 0
          Tom
          1. 0
            Christoph Jeschke
            1. 0
              Tom
            2. 0
              Simone
              1. 0
                Christoph Jeschke
                1. 0
                  Tom
                  1. 0
                    Christoph Jeschke
                    • menschelei
                    1. 0
                      Tom
                      1. 0
                        Christoph Jeschke
                        1. 0
                          Tom
                  2. 0
                    Sven Rautenberg
                    1. 0
                      Tom
                      1. 0
                        EKKi
                        • meinung
          2. 2

            Lebensdauer von {Datenbank-,File-,...}Handles in PHP

            Christian Seiler
            1. 0
              Tom
              1. 0
                Sven Rautenberg
                1. 0

                  Lebensdauer von {Datenbank-,File-,...} Handles in PHP

                  Tom
                  1. 0
                    EKKi
                    • meinung
                    1. 0
                      Tom
                2. 0
                  Edgar Ehritt
            2. 0
              Tom
              1. 0
                EKKi
                • meinung
                1. 0

                  Genau nachfragen ist hier verboten?

                  Tom
                  • zu diesem forum
                  1. 0
                    flowh
                    1. 0
                      Tom
                      1. 0
                        Edgar Ehritt
                        1. 0
                          Tom
              2. 8

                Interna von Variablen und Ressourcen, Lebensdauer, GC

                Christian Seiler
                1. 0
                  Tom
                2. 0

                  Wo werden Name und (Meta-)Daten der Variable verbunden?

                  Tom
                  1. 0
                    dedlfix
                    1. 0
                      Tom
                      1. 0
                        dedlfix
                        1. 0
                          Tom
                          1. 0
                            dedlfix
                            1. 0
                              Tom
                              1. 0
                                Christian Seiler
                  2. 0
                    Christian Seiler
                    1. 0
                      Christian Seiler
                      1. 0
                        Tom
                3. 0
                  Edgar Ehritt
                  • meinung
                  1. 0
                    Christian Seiler
                    1. 0

                      Interna von Variablen

                      Edgar Ehritt
                      • menschelei
                      1. 0
                        Christian Seiler
                    2. 0
                      Tom
                4. 0
                  Bademeister
                  1. 0
                    Christian Seiler
                    1. 0
                      Bademeister
                      1. 0
                        Christian Seiler
                    2. 0
                      Kai345
  2. 0
    Edgar Ehritt