Ankündigung

Einklappen
Keine Ankündigung bisher.

4.1: Don't panic! The Hitchhiker's Guide to PHP oddities

Einklappen
X
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

  • 4.1: Don't panic! The Hitchhiker's Guide to PHP oddities

    4,1:
    Das Jahr geht langsam zur Neige, draußen wird es immer kälter und dunkler. Wer abends nicht mehr unbedingt vor die Tür muss, bleibt lieber zu Hause, trinkt Kakao, isst Lebkuchen und Christstollen, bastelt kleine Tiere aus Kastanien und sieht dicken roten Wachskerzen beim Runterbrennen zu.

    Diese vorweihnachtliche Atmosphäre ist ja ganz wunderbar friedvoll, aber mitunter eben auch ein wenig langweilig. Höchste Zeit also, die grauen Zellen ein wenig in Schwung zu bringen und sich mit Programmierung zu beschäftigen, genauer: mit einigen der merkwürdigeren oder oftmals verwirrenden Punkten der PHP-Programmierung.


    Vergleiche mit Fließkommazahlen

    Fangen wir einfach an. Was liefert die folgende Zeile?
    PHP-Code:
    var_dump((0.1+0.5)*10 == 6); 
    Klar, die Rückgabe ist bool(true), wie leicht nachzurechnen ist. Aber wie sieht es bei folgender Rechnung aus?
    PHP-Code:
    var_dump((0.1+0.7)*10 == 8); 
    Hier erhalten wir bool(false). Die PHP-Dokumentation weiß dazu:
    It is typical that simple decimal fractions like 0.1 or 0.7 cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9.

    […]

    So never trust floating number results to the last digit, and never compare floating point numbers for equality. [Hervorhebung hinzugefügt.]
    Ein guter Rat, der natürlich nicht nur für die Sprache PHP gilt.


    Copy-on-write und Call-by-reference

    PHP nutzt die Copy-on-write-Strategie bei der Speicherverwaltung für Variableninhalte. Nach dieser Strategie werden Inhalte erst dann physisch kopiert, wenn ein Schreibzugriff erfolgt.
    PHP-Code:
    $a str_repeat('X'1024 1024); // ~1 MB Speicherverbrauch
    $b $a;                           // ~1 MB Speicherverbrauch
    $b .= 'X';                         // ~2 MB Speicherverbrauch (Copy-on-write) 
    Allgemein ist es deshalb auch bei Funktionsaufrufen unnötig, Parameter als Referenzen anzugeben, nur um Speicherplatz zu sparen. Solange im Funktionskörper der Inhalt der übergebenen Variablen nicht verändert wird, verweist die Variable auf denselben Speicherinhalt wie ihr Pendant außerhalb des Funktionskörpers, wodurch kein zusätzlicher Speicherplatz verbraucht wird.

    Die Parameterübergabe sowohl von primitiven Datentypen (float, string, …) als auch von Objekten geschieht standardmäßig als Call-by-value. Auch Objekte werden nur dann by-reference übergeben, wenn dies durch das &-Zeichen in der Funktionssignatur explizit vermerkt ist. Der Inhalt (im Sinne von value) einer Objektvariable sind nicht die Instanzdaten selbst, sondern eine Art Ressourcen-Kennung (Object-Identifier), über die PHP auf die Instanzdaten zugreift.
    PHP-Code:
    // Call-by-value
    function f($a) {
        
    $a null;
    }

    // Call-by-reference
    function g(&$a) {
        
    $a null;
    }

    $s 'foo'f($s); var_dump($s); // string(3) "foo"
    $s 'foo'g($s); var_dump($s); // NULL

    $o = new stdClass(); f($o); var_dump($o); // object(stdClass)#1 (0) { }
    $o = new stdClass(); g($o); var_dump($o); // NULL 

    Inkrement-Operatoren in Berechnungen

    Achtung: Die folgenden Angaben in diesem Abschnitt sind in hohem Maße spekulativ und stellen eine Analyse der Symptome dar, keine fundierte Erklärung. Einfache Tests haben ergeben, dass verschiedene Programmiersprachen (C, Java, PHP) Ausdrücke mit Inkrement-Operatoren auf sehr unterschiedliche Weise auswerten. Es kann an dieser Stelle deshalb nur empfohlen werden, im Umgang mit ihnen besondere Vorsicht walten zu lassen und Befehlsketten mit „Side-Effect-Operatoren“, also Wertveränderungen während der Auswertung, möglichst in mehrere Einzelbefehle aufzuspalten, da das Verhalten an dieser Stelle nicht immer definiert oder vorhersehbar ist.

    Handelte es sich bei den beiden vorherigen Punkten um allgemeingültiges Wissen, so geht es hier um eine eher PHP-spezifische Eigenart. Was liefert dieser Ausdruck?
    PHP-Code:
    $a 1;
    var_dump($a + ++$a); 
    Zu erwarten wäre wohl int(3) (1+2), tatsächlich ist das Ergebnis aber int(4) (2+2).

    Dagegen liefert dieser Ausdruck
    PHP-Code:
    $a 1;
    var_dump($a + ++$a); 
    dann schließlich doch das erwartete int(3).

    Dieses eigenartige Verhalten ist wahrscheinlich darauf zurückzuführen, dass PHP variable Werte in Berechnungen erst zum spätest möglichen Zeitpunkt in ihren jeweiligen Wert auflöst, um temporäre Variablen zu sparen. Im ersten Ausdruck wird ($a + (++$a)) gerechnet, im zweiten (($a + 0) + (++$a)). Die Auswertung der Klammern findet von innen nach außen und auf gleicher Ebene von links nach rechts statt.

    Konsequenterweise ergibt
    PHP-Code:
    $a 1;
    var_dump($a + (+ ++$a)); 
    dann wiederum int(4). Assoziativgesetz, we hardly knew ye.

    (Eine Diskussion dieses Themas fand unter anderem in diesem Thread statt.)


    Referenzen und foreach-Schleifen

    Ein weiterer Klassiker, dem jeder früher oder später begegnet, ist der Einsatz von Referenzen innerhalb von foreach-Schleifen. Die Kurzversion:
    PHP-Code:
    $a range(15);

    foreach (
    $a as &$ref);
    var_dump(implode(', '$a));

    foreach (
    $a as $ref);
    var_dump(implode(', '$a)); 
    Die Rückgabe sieht so aus (man beachte die 4 am Ende der zweiten Zeile):
    Code:
    string(13) "1, 2, 3, 4, 5"
    string(13) "1, 2, 3, 4, 4"
    Nach dem Durchlauf der ersten Schleife ist $ref eine Referenz auf das letzte Array-Element. Das Verändern des Inhalts von $ref würde also gleichzeitig den Inhalt des letzten Array-Elements verändern. Genau das geschieht in der zweiten foreach-Schleife, in der $ref, also auch das letzte Array-Element, der Reihe nach auf jedes vorherige Element von $a gesetzt wird und in einem letzten Schritt auf sich selbst.

    In diese „Falle“ ist immer dann leicht zu tappen, wenn der Inhalt eines Arrays ausgegeben, aber vorher noch geringfügig angepasst werden soll.
    PHP-Code:
    // Beispiel von: http://www.selfphp.de/forum/showthread.php?t=23503

    $filme = array('film1''film2''film3');

    foreach (
    $filme as &$film)
      
    $film ucfirst($film);

    foreach (
    $filme as $key=>$film)
      echo 
    "$key $film <br />"
    Abhilfe schafft unset($film); nach der ersten Schleife.


    Unerreichbare Array-Indizes

    Zum Abschluss noch ein Beispiel aus der Abteilung „Kurioses“, das vielleicht sogar als Bug bezeichnet werden könnte.
    PHP-Code:
    $a = array('10' => 'foo');

    $b = (array) json_decode(json_encode($a));

    $b['10'] = 'bar';
    $b[10] = 'baz';

    var_dump($b);                  // array(2) { ["10"]=> string(3) "foo"
                                   //            [10]=> string(3) "baz" }

    var_dump($b[10], $b['10']);    // string(3) "baz" string(3) "baz" 
    Der Aufruf der beiden JSON-Funktionen erstellt ein Objekt mit einer öffentlichen Instanzvariable $10. Diese wird als Index in das Array gesetzt, ohne dass eine Typenkonvertierung stattfindet. Der Array-Index string("10") ist nun nicht mehr ansprechbar, da jeder Zugriffsversuch vom Interpreter auf den Index int(10) weitergeleitet wird.

    Es ist allerdings möglich, auf eine öffentliche Instanzvariable $10 zuzugreifen.
    PHP-Code:
    $a = array('10' => 'foo');

    $b json_decode(json_encode($a));

    var_dump($b->{'10'});    // string(3) "foo" 


  • #2
    Vielen dank - wieder ein paar Dinge dazugelernt


    btw.: Nach welchem System sind die einzelnen Adventskalender-'Türchen' dieses Jahr denn numeriert? Die letzten beiden Jahre war es ja leicht, aber dieses Jahr komm ich einfach nicht drauf.

    Kommentar


    • #3
      Mal wieder sehr schöner Beitrag.

      Vielen Dank!
      "My software never has bugs, it just develops random features."
      "Real programmers don't comment. If it was hard to write, it should be hard to understand!"

      Kommentar


      • #4
        Zitat von DarkWarrior Beitrag anzeigen
        btw.: Nach welchem System sind die einzelnen Adventskalender-'Türchen' dieses Jahr denn numeriert? Die letzten beiden Jahre war es ja leicht, aber dieses Jahr komm ich einfach nicht drauf.
        PHP-Code:
        round(sqrt(date('j')), 1); 

        Kommentar


        • #5
          Zitat von zwutz Beitrag anzeigen
          PHP-Code:
          round(sqrt(date('j')), 1); 
          Ähhhm.. nein! Das passt nur zufällig gestern und heute

          Tipp: Schau dir mal an, welche Tage ganze Zahlen haben
          @fschmengler - @fschmengler - @schmengler
          PHP Blog - Magento Entwicklung - CSS Ribbon Generator

          Kommentar


          • #6
            Aufgefallen ist es mir auch schon mit dem ganzen zahlen... Aber zu einem Ergebnis komm ich trotzdem nicht...

            Code:
            1  : 0
            2  : 1
            3  : 1,58
            4  : 2
            5  : 2,32
            6  : 2,58
            7  : 2,81
            8  : 3
            9  : 3,17
            10 : 3,32
            11 : 3,46
            12 : 3,58
            13 : 3,7
            14: 3,8
            15: 3,91
            16: 4
            17: 4,1
            18: ???
            "My software never has bugs, it just develops random features."
            "Real programmers don't comment. If it was hard to write, it should be hard to understand!"

            Kommentar


            • #7
              log_2(tag) würde ich mal raten^^

              Kommentar


              • #8
                lol hab ich ja mal gar ner dran gedacht
                "My software never has bugs, it just develops random features."
                "Real programmers don't comment. If it was hard to write, it should be hard to understand!"

                Kommentar

                Lädt...
                X