Изучаем PHP изнутри. Zval

    Эта статья базируется на главе Zvals книги PHP Internals Book, переводом которой на русский язык я сейчас занимаюсь [1]. Книга ориентирована в первую очередь на C-программистов, желающих писать свои расширения для PHP, но, я уверен, что она окажется полезной и для PHP-разработчиков, так как описывает внутреннюю логику работы интерпретатора. В статье я оставил только базовую теорию, которая должна быть понятна всем разработчикам (даже не знакомым с PHP или C). За более полным изложением материала обратитесь к книге.

    Задачка для привлечения внимания. Каким будет результат выполнения следующего кода?
    $obj1 = new StdClass();
    $obj2 = new StdClass();
    
    $obj1->value = 1;
    $obj2->value = 1;
    
    function f1($o) {
      $o = 100;
    }
    
    function f2($o) {
      $o->value = 100;
    }
    
    f1($obj1);
    f2($obj2);
    
    var_dump($obj1);
    var_dump($obj2);
    


    Ответ
    object(stdClass)#1 (1) { [«value»]=> int(1) }
    object(stdClass)#2 (1) { [«value»]=> int(100) }

    Если вы точно определили ответ и можете объяснить почему он будет именно таким, то, наверное, вы не узнаете из этой статьи ничего нового, иначе — вам определенно стоит прочитать эту статью, чтобы углубить свои знания.

    Базовая структура

    Базовой структурой данных в PHP является zval (сокращение от «Zend value»). Каждый zval хранит в себе несколько полей, два из которых — значение и тип этого значения. Это необходимо потому что PHP — это язык с динамической типизацией и поэтому тип переменных известен только во время выполнения программы, а не во время компиляции. Кроме того, тип переменной может быть изменен в течении жизни zval, то есть zval ранее хранимый как целое число позднее может содержать строку.

    Тип переменной хранится как целочисленная метка (type tag, unsigned char). Метка может принимать одно из 8 значений, которое соответствует 8 типам данных доступных в PHP. Эти значения должны присваиваться с использованием констант вида IS_TYPE. Например, IS_NULL соответствует типу данных null, а IS_STRING — строке.

    zvalue_value

    Фактическое значение переменной хранится в типе данных union («объединение», в дальнейшем я буду использовать термины union или юнион), который определен следующим образом:
    typedef union _zvalue_value {
        long lval;
        double dval;
        struct {
            char *val;
            int len;
        } str;
        HashTable *ht;
        zend_object_value obj;
    } zvalue_value;
    

    Небольшое пояснение для тех кто не знаком с концепцией union-ов. Union определяет несколько членов-данных различных типов, но в каждый момент времени может использоваться только одно значение из определенных в юнионе. Например, если члену данных value.lval было присвоено значение, то для доступа к данным вы можете использовать только value.lval, доступ к другим членам данных недопустим и может приводить к непредсказуемому поведению программы. Причина этого в том, что юнионы хранят данные всех своих членов в одной области памяти и интерпретируют значение по разному исходя из имени, к которому вы обращаетесь. Размер памяти, выделяемой для юниона, соответствует размеру самого большого его члена-данных.

    При работе с zval-ами используется специальная метка (type tag), которая позволяет определить какой тип данных хранится в юнионе в данный момент. Прежде чем обратиться к API давайте посмотрим какие типы данных поддерживаются в PHP и как они хранятся.

    Простейший тип данных — IS_NULL: он не должен хранить какое-либо значение, так как это просто null.

    Для хранения чисел PHP представляет 2 типа: IS_LONG и IS_DOUBLE, которые используют члены long lval и double dval соответственно. Первый используется для хранения целых чисел, второй — для чисел с плавающей точкой.

    Есть несеколько вещей, которые вам следует знать о типе данных long. Во-первых, это signed integer, то есть он может содержать положительные и отрицательные значения, но этот тип данных не подходит для побитовых операций. Во-вторых, long имеет разные размеры на разных платформах: на 32-битных системах он имеет размер 32 бита или 4 байта, но на 64-битных системах он может иметь размер как 4, так и 8 байт. В Unix-системах он обычно имеет размер в 8 байт, в то время как в 64-битных версиях Windows использует только 4 байта.

    По этой причине вы не должны полагаться на конкретное значение типа long. Минимальное и максимальное значения, которые могут быть сохранены в типе данных long доступны в константах LONG_MIN и LONG_MAX и размер этого типа может быть определен с использованием макро SIZEOF_LONG (в отличии от sizeof(long) этот макро может быть использован и в #if директивах).

    Тип данных double предназначен для хранения чисел с плавающей точкой и, обычно, следуя спецификации IEEE-754, он имеет размер в 8 байт. Детали этого формата не будут обсуждаться здесь, но вам как минимум следует знать, что этот тип имеет ограниченную точность и часто хранит не точно то значение, на которое вы рассчитываете.

    Булевы переменные используют флаг IS_BOOL и хранятся в поле long val как значения 0 (false) и 1 (true). Так как этот тип использут только 2 значения, то, теоретически, достаточно было использовать тип меньшего размера (например zend_bool), но так как zvalue_value — это юнион и под него и так выделен объем памяти соответствующий самому большому члену данных, то применение более компактной переменной для булевых значений не приведет к экономии памяти. Поэтому lval повторно использован в этом случае.

    Строки (IS_STRING) хранятся в структуре struct {char *val; int len; } str;, то есть строка хранится как указатель на строку char * и целочисленная длина строки int. Строки в PHP должны явно хранить свою длину для того чтобы иметь возможность содержать NUL байты (\0) и быть бинарно безопасными (binary safe). Но несмотря на это, строки используемые в PHP все равно заканчиваются нулевым байтом (NUL-terminated), чтобы обеспечить совместимость с библиотечными функциями, которые не принимают аргумент с длиной строки, а ожидают найти нулевой байт в конце строки. Конечно, в таких случаях строки больше не могут быть бинарно безопасными и будут обрезаны до первого вхождения нулевого байта. Например, много функций связанных с файловой системой и большинство строковых функций из libc ведут себя подобным образом.

    Длина строки измеряется в байтах (не числом Unicode-символов) и не должно включать нулевой байт, то есть длина строки foo равна 3, несмотря на то, что для её хранения используется 4 байта. Если вы оперделяете длину строки с использованием sizeof вам нужно вычитать единицу: strlen("foo") == sizeof("foo") - 1.

    Очень важно понимать: длина строки хранится в типе int, а не в long или каком-то другом похожем типе. Это исторический артефакт, который ограничивает длину строки 2147483647 байтами (2 гигабайта). Строки большего размера будут причиной переполнения (что сделает их длину отрицательной).

    Оставшиеся три типа будут упомянуты лишь поверхностно.

    Массивы используют метку IS_ARRAY и хранятся в члене-данных HashTable *ht. Как работает структура данных HashTable рассмотрено в другой статье.

    Объекты (IS_OBJECT) исползуют член-данных zend_object_value obj, который состоит из «object handle» (целочисленный ID, используемый для поиска реальных данных) и набора «object handlers», которые определяют поведение объекта. Система классов и объектов в PHP будет описана в главе «Классы и объекты».

    Ресурсы (IS_RESOURCE) похожи на объекты, так как они также хранят уникальный ID, используемый для поиска значения. Этот ID хранится в члене long lval. Ресурсы будут описаны в соответствующей главе, которая пока не написана.

    Подведем промежуточный итог, ниже представлена таблица с перечислением всех доступных меток типов и соответствующее им хранилище значений:
    Type tag Storage location
    IS_NULL none
    IS_BOOL long lval
    IS_LONG long lval
    IS_DOUBLE double dval
    IS_STRING struct { char *val; int len; } str
    IS_ARRAY HashTable *ht
    IS_OBJECT zend_object_value obj
    IS_RESOURCE long lval

    zval

    Давайте теперь посмотрим как выглядит структура данных zval:

    typedef struct _zval_struct {
        zvalue_value value;
        zend_uint refcount__gc;
        zend_uchar type;
        zend_uchar is_ref__gc;
    } zval;
    
    

    Как уже упоминалось, zval содержит члены для хренения значения и его типа. Значение хранится в юнионе zvalue_value, который описан выше. Тип хранится в zend_uchar type. Кроме того эта структура содержит 2 дополнительных свойства, имена которых заканчиваются на __gc, которые используются механизмом сборки мусора. Подробнее эти свойства рассмотрены в следующем разделе.

    Управление памятью

    Структура данных zval играет 2 роли. Во-первых, как было описано в предыдущем разделе, она хранит данные и их тип. Во-вторых (это будет рассмотрено в текущем разделе) используется для эффективного управления значениями в памяти.

    В этом разделе мы рассмотрим концепции подсчета ссылок и копирования-при-записи (copy-on-write).

    Семантика знечений и ссылок

    В PHP все значения всегда имеют семантику значений (value-semantics), только если вы явно не запросили использование ссылок. Это значит, что и при передаче значения в функцию, и при выполнении операции присваивания вы будете работать с 2 разными копиями значения. Пара примеров поможет убедиться в этом:
    <?php
    $a = 1;
    $b = $a;
    $a++;
    
    // Только $a будет увеличена на 1, $b сохранит исходное значение:
    var_dump($a, $b); // int(2), int(1)
    
    function inc($n) {
        $n++;
    }
    
    $c = 1;
    inc($c);
    
    // Значение переменной $c снаружи функции и значение $n  внутри функции — это разные значения
    var_dump($c); // int(1)
    

    Пример выше очень прост и очевиден, но важно понимать, что это основное правило, применяемое везде. Оно также применимо и к объектам:
    <?php
    $obj = (object) ['value' => 1];
    
    function fnByVal($val) {
        // Меняется не только значение переменной, но и тип с object на integer
        $val = 100;
    }
    
    function fnByRef(&$ref) {
        $ref = 100;
    }
    
    // Функция, в которую передано значение, не изменила $obj, а функция в которую передана ссылка — изменила:
    fnByVal($obj);
    var_dump($obj); // stdClass(value => 1), функция fnByVal не изменила переданный оъект
    fnByRef($obj);
    var_dump($obj); // int(100)
    

    Часто можно услышать, что в PHP 5 объекты автоматически передаются по ссылке, но пример выше показывает, что это не так. Функция, в которую передается значение не может изменить значение переданной переменной, только функция, в которую передается ссылка, может сделать это.

    Это так и есть, хотя объекты действительно ведут себя так, будто они переданы по ссылке. Вы не можете присвоить переменной другое значение, но вы можете менять свойства объекта. Это возможно потому, что значением объекта является ID, который используется для поиска «реальных данных» объекта. Семантика передачи по значению не даст вам изменить этот ID на другой или поменять тип переменной, но она не помешает вам изменить «реальные данные» объекта.

    Немного изменим пример выше:

    <?php
    $obj = (object) ['value' => 1];
    
    function fnByVal($val) {
        // Теперь мы не меняем тип переменной, а только одно из свойств объекта
        $val->value = 100;
    }
    
    var_dump($obj); // stdClass(value => 1)
    fnByVal($obj);
    var_dump($obj); // stdClass(value => 100), функция fnByVal изменила свойство в переданном объекте
    
    

    То же самое можно сказать и про ресурсы, так как они тоже хранят только ID, который может быть использован для поиска данных. Итак, еще раз, семантика передачи по значению не дает вам изменить ID или тип zval-а, но не мешает вам сменить данные ресурса (например сдвинуть позицию в файле).

    Подсчет ссылок и копирование-при-записи

    Если вы немного поразмышляете о написанном выше, то вы придете к заключению, что PHP должен выполнять огромное число операций копирования. Каждый раз передавая переменную в функцию её значение должно быть скопировано. Это может не быть проблемой для данных типа integer или double, но представьте, что вы передаете в функцию массив, содержащий десять миллионов значений. Копирование миллионов значений при каждом вызове функции — это недопустимо медленно.

    Для того чтобы избежать этого в PHP используется парадигма копирования-при-записи (copy-on-write). Zval может совместно использоваться множеством переменных/функций/и т.д., но только до тех пор пока данные zval-а используются для чтения. Как только кто-то захочет изменить данные zval-а, он будет скоприрован прежде чем изменения будут применены.

    Так как один zval может быть использован в нескольких местах, PHP должен иметь возможность определить момент, кода zval больше никем не используется и удалить его (освободить занимаемую им память). PHP делает это простым подсчетом ссылок. Учтите, что «ссылка» здесь это не ссылка в терминах PHP (та, что задается при помощи &), а просто показатель, говорящий что кто-то (переменная, функция, и т.д.) использует этот zval. Число таких ссылок называется refcount и оно хранится в члене-данных refcount__gc zval-а.

    Чтобы понять как это работает давайте рассмотрим пример:

    <?php
    $a = 1;    // $a =           zval_1(value=1, refcount=1)
    $b = $a;   // $a = $b =      zval_1(value=1, refcount=2)
    $c = $b;   // $a = $b = $c = zval_1(value=1, refcount=3)
    
    $a++;      // $b = $c = zval_1(value=1, refcount=2)
               // $a =      zval_2(value=2, refcount=1)
    
    unset($b); // $c = zval_1(value=1, refcount=1)
               // $a = zval_2(value=2, refcount=1)
    
    unset($c); // zval_1 уничтожен, так как refcount=0
               // $a = zval_2(value=2, refcount=1)
    
    

    Логика здесь простая: когда ссылка добавляется, значение refcount увеличивается на единицу, когда ссылка удаляется — refcount уменьшается. Когда значение refcount достигает 0 — zval удаляется.

    Правда, этот метод не будет работать в случае циклических ссылок:

    <?php
    $a = []; // $a = zval_1(value=[], refcount=1)
    $b = []; // $b = zval_2(value=[], refcount=1)
    
    $a[0] = $b; // $a = zval_1(value=[0 => zval_2], refcount=1)
                // $b = zval_2(value=[], refcount=2)
                // refcount zval_2 увеличен так как
                // он использован в массиве zval_1
    
    $b[0] = $a; // $a = zval_1(value=[0 => zval_2], refcount=2)
                // $b = zval_2(value=[0 => zval_1], refcount=2)
                // refcount zval_1 увеличен так как
                // он использован в массиве zval_2
    
    unset($a);  //      zval_1(value=[0 => zval_2], refcount=1)
                // $b = zval_2(value=[0 => zval_1], refcount=2)
                // refcount  zval_1 уменьшен, но zval
                // продолжит существовать так как на него все еще ссылается zval_2
    
    unset($b);  //      zval_1(value=[0 => zval_2], refcount=1)
                //      zval_2(value=[0 => zval_1], refcount=1)
                // refcount zval_2 уменьшен, но
                // продолжит существовать так как на него все еще ссылается zval_1
    
    

    После того как код приведенный выше будет запущен мы получим ситуацию, в которой у нас будет два zval-а недоступных ни через одну переменную, но все еще существующих в памяти, так как они ссылаются друг на друга. Это классический пример проблемы с подсчетом ссылок.

    Для решения этой проблемы в PHP реализован еще один механизм сборки мусора — циклический сборщик. Мы можем его сейчас проигнорировать так как циклический сборщик (в отличии от механизма подсчета ссылок) прозрачен для разработчиков расширений PHP. Если вам интересна эта тема, то обратитесь к документации PHP, в которой описан этот алгоритм.

    Есть еще одна особенность PHP-ссылок (тех, что определяются как &$var, а не тех, что были рассмотрены выше), которая должна быть рассмотрена. Для того чтобы обозначить, что zval используется как PHP-ссылка используется флаг is_ref__gc в структуре zval.

    Если is_ref=1 это является сигналом к тому, что zval не должен быть скопирован перед модификацией, вместо этого должно быть изменено значение zval-а:

    <?php
    $a = 1;   // $a =      zval_1(value=1, refcount=1, is_ref=0)
    $b =& $a; // $a = $b = zval_1(value=1, refcount=2, is_ref=1)
    
    $b++;     // $a = $b = zval_1(value=2, refcount=2, is_ref=1)
              // Так как is_ref=1 PHP напрямую изменяет zval
              // вместо того чтобы делать его копию
    
    

    В примере выше zval переменной $a перед созданием ссылки имеет refcount=1. Теперь рассмотрим похожий пример с числом ссылок большим чем 1:

    <?php
    $a = 1;   // $a =           zval_1(value=1, refcount=1, is_ref=0)
    $b = $a;  // $a = $b =      zval_1(value=1, refcount=2, is_ref=0)
    $c = $b   // $a = $b = $c = zval_1(value=1, refcount=3, is_ref=0)
    
    $d =& $c; // $a = $b = zval_1(value=1, refcount=2, is_ref=0)
              // $c = $d = zval_2(value=1, refcount=2, is_ref=1)
              // $d это ссылка $c, но *не* на $a and $b, поэтому
              // zval здесь должен быть скопирован. Теперь у нас есть
              // один zval с is_ref=0 и один is_ref=1.
    
    $d++;     // $a = $b = zval_1(value=1, refcount=2, is_ref=0)
              // $c = $d = zval_2(value=2, refcount=2, is_ref=1)
              // Так как мы имеем дело с 2 независимыми zvals $d++ не
              // меняет $a и $b (как и ожидалось).
    
    

    Как вы видите, при создании ссылки на zval c is_ref=0 и refcount>1 требует создания копии. Аналогично, при использовании zval с is_ref=1 и refcount>1 в контексте с передачей по значению потребуется операция копирования. По этой причине использование PHP-ссылок обычно замедляет код. Почти все функции в PHP используют семантику передачи по значению, поэтому они создают копию при получении zval со значением is_ref=1.

    Заключение

    В этой статье я привел выжимку главы Zvals книги PHP Internals Book. Я постарался оставить только тот материал, который будет полезен PHP-разработчикам и вырезал много текста, связанного с разработкой расширений (иначе объем статьи вырос бы раза в 3). Если вам интересно глубже изучить вопрос разработки расширений для PHP вы можете обратиться к книге или моему переводу. На данный момент переведена только глава Zvals, но я продолжаю работу. В ближайшее время займусь интереснейшими главами про хештаблицы и классы.



    [1] Перевод книги делается с разрешения авторов, но он является неофициальным. Почитать мой перевод можно тут: romka.gitbooks.io/php-internals-book-ru, помочь переводить тут: github.com/romka/phpinternalsbook-ru
    Метки:
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 37
    • +3
      Be aware: в новом phpng (потенциальный php 5.7) все будет немного по-другому.
      • +4
        +1. Кстати, именно этот доклад побудил меня перевести эту книгу. Я слушал этот доклад и в зале слышал комментарии типа: «о чем это?», «ничего непонятно». То есть не все PHP-разработчики задумываются о том как работает PHP, а это знание может быть полезным.
        • 0
          Доклад был отличный, это да! А про zval и остальное на русском действительно многим полезно почитать будет, включая меня самого :)
          Насчет phpng — я так понял (вполне возможно, что неправильно), что прирост производительности при работе с HashTable будет в каких-то одних случаях, а в других будет что-то вроде fallback к старому варианту и соответственно никакой выгоды. Но я не запомнил, о каких случаях шла речь. Буду рад, если кто подскажет.
          • +6
            Тут вот какой кейс — часто пхп-шный «массив» используется как настоящий массив (как массив в си например), поэтому сделана такая фича:
            если HashTable.u.flags | HASH_FLAG_PACKED > 0, значит что элементы массива располагаются просто по порядку (скорее всего он был собран как-нибудь так $ar = []; $ar[] = 1; $ar[] = 42; и т.д.), а значит мы не нуждаемся в хеш таблице как таковой => HashTable.arHash — не инициализирован в данный момент (тут мы экономим память) и если юзер пытается получить доступ к какому-либо элементу, вместо хеширования и лукапа в хеш-таблицу нам надо лишь получить элемент по оффсету (предварительно проверив, конечно, что ключ — не out of bounds), что сэкономит немного времени. Если юзер решит добавить элемент со строковым ключом, или с численным ключом значение которого не равно HashTable.nNextFreeElement — то делаем rehash с убранным флагом HASH_FLAG_PACKED.
            • +3
              Спасибо за замечательное разъяснение! Теперь до меня дошло.
              • 0
                А как только добавили элемент не по порядку, это всё начнёт копироваться в хеш?
                • +3
                  И да и нет, на самом деле там несколько условий, проще будет объяснить в коде:

                  function set($arr, $key, $val)
                  {
                  ...
                  if ($arr->flags & HASH_FLAG_PACKED) {
                      if ($key < 0) {
                          goto convert_to_hash;
                      }
                  
                      if ($key < $arr->lastUsedKey) {
                           if ($arr->data[$key]->type !== TYPE_VAR_UNDEFINED) { // TYPE_VAR_UNDEFINED в данном случае значит что это неинициализированная переменная (ей ничего не присвоили, даже null)
                               // добавим этот элемент в массив, он должен нормально туда влезть, заменив какой-то из уже существующих по этому индексу
                           } else {
                               // если по такому ключу ничего нет (но при этом после него есть ключи, которые мы использовали), то превращаемся в хештаблицу, т.к. только с ней мы сможем соблюсти такой порядок элементов
                               goto convert_to_hash;
                           }
                      } elseif ($key < $arr->tableSize) { // если ключ больше самого большого ключа из этого массива, но меньше чем кол-во заранее инициализированных бакетов (это число всегда кратно 2)
                          // просто добавим этот элемент сюда
                      } elseif ($key < $arr->tableSize * 2 && $arr->tableSize - $arr->lastUsedKey < $arr->tableSize / 2) { // если ключ больше чем кол-во преинициализированных бакетов, но меньше чем это же число умноженное на два, и при этом если мы уже используем больше половины бакетов
                          // инициализировать в два раза больше бакетов чем есть сейчас, и переместить туда их
                          // добавить элемент
                      } else {
                          goto convert_to_hash;
                      }
                  }
                  convert_to_hash:
                  ...
                  }
                  


                  Вроде ничего не забыл, в общем-то это выжимка из этого кода, почитайте лучше его, если закомы с си и пхп на том уровне.
                  • 0
                    Ага, спасибо! Почитаю. Меня этот момент беспокоит, на самом деле. Так как неожиданно скорость вставки одного элемента может резко упасть.
            • 0
              Да доклад был замечательный. Жаль видео записи нет. А по поводу того что не все PHP-разработчики задумываются о том как работает PHP можно даже по другому сказать( более эпичнее что ли ), Не более 10-20 процентов знают что такое Zend Engine, и не более 5 процентов знают что такое zval, а уж про то сколько всего разработчиков понимают как оно работает и вовсе молчу. я сам грешен буквально пару лет назад из 6 лет работы с php узнал что такое zval и как он работает. И более чем уверен что у меня ещё есть прорехи в знаниях. Кстати может кто нибудь сам записывал доклад на видео? А то похоже от организаторов не дождёмся. Там даже презентации не все есть.

              Кстати rrromka приведённый в начале пример не имеет никакого отношения к Zval.
              • +1
                Кстати rrromka приведённый в начале пример не имеет никакого отношения к Zval.

                Почему? Я этим примером хотел показать, что функция f1 меняет тип переданного zval-а, по этому внутри неё срабатывает copy-on-write и объект снаружи функции остается без изменения. Функция f2 тип не меняет, по этому копироания-при-записи нет и меняется zval снаружи функции.
                • +2
                  Начнём с того что наверное для меня такое поведение очевидно даже без знания Zval.

                  А далее причём тут вообще тип?
                  • –2
                    Я согласен с тем, что программируя на PHP можно обойтись без знания о том что такое zval. Но если вы хотите разобраться почему именно в приведенном примере функции f1 и f2 ведут себя по разному, то понимать структуру zval и zvalue_value нужно.

                    Отличие между этими двумя функциями на уровне zval-ов заключается в том, что первая меняет lval и type переданного объекта, что вызывает copy-on-write. Вторая не трогает lval и type, а работает только с zend_object_value.

                    Как бы вы объяснили разницу в работе этих функций без упоминания zval-ов?
                    • +3
                      Ну это понятно из док. Обе функции принимают значения. Но объекты — это ссылочный тип (тип значения). Так что в первой вы меняете переменную (меняете ее значение) в другом скоупе, поэтому она и не изменяется в глобальном. А во второй функции вы работаете с самим объектом (значением аргумента), который является ссылкой. Как-то запутанно это объяснять, но по-моему это не пересекается со знаниями о том, как это устроено внутри.
          • +2
            Ага, только в следующей версии внутреннея структура уже будет другой.
            • +1
              А зачем вешать специфическую статью про PHP в общий хаб «Программирование»?
              • +3
                Непонятная логика. Следуя ей статьи про Java, C++, Haskell и другие языки тоже не нужно в этот хаб вешать.
                • +7
                  Ключевое слово здесь «специфическую».

                  Товарищ Zigmar, видимо, хотел сказать нам, что общий хаб «Программирование» задуман для повествования о вещах в том или ином виде применимых в большинстве языков, а для описания узко-специфичных конструкций одного конкретного языка существуют специализированные хабы.

                  С уважением.
                  • +5
                    Именно. Вывешивание узконаправленных статей в общие хабы ломает систему фильтрации. Я например подписан на хабы языков, которые мне интересны плюс на хаб «программирование» по общепрограммисткой тематике. Если все статьи вешать и туда и туда, то не будет способа отфильтровать интересующий вас контент.
              • 0
                Ещё года два назад на собеседованиях любили спрашивать: «Какова алгоритмическая сложность (в О-нотации) функции strlen($s) в PHP»

                Надеюсь, прочитавшие эту статью сразу смогут ответить на этот вопрос.
                • 0
                  А что (вопрошающие) считали правильным ответом? O(N) или O(1)?
                  • +1
                    Вопрошающие знали ответ O(1), но я не уверен в том, что все знали почему.

                    В одной компании раскритиковали мой ответ: \Theta(1).
                    • 0
                      Для того, чтобы предположить правильный ответ на этот вопрос, мне достаточно знать, что в строке может содержаться нулевой байт. Если это так, то в большинстве случаев строка состоит из пары (указатель на начало строки, длина строки). Для людей, пишущих на C, причины именно такого решения понятны.

                      Правда, если вы предположите такое в отношении zsh (а там ноль может быть внутри строки, если только вы не запретили ему там быть с помощью настроек), то вы прогадаете, но других языков, использующих для хранения бинарных строк экранирование я просто не знаю.
                • +7
                  Я ответ определил сразу и не вижу никаких причин по которым он может быть иным.
                  При этом с тонкостями zval не знаком и не вижу, как они связаны с этим примером.
                  • 0
                    Плюсую, потому что эти вещи довольно чётко описаны в руководстве по языку и для их понимания о внутренностях знать не обязательно.
                    • +1
                      Зато вопрос в комментарии выше вы не определите без знания внутренностей. А это знание довольно полезно.
                      • –2
                        А его не надо определять, можно просто помнить.
                        • +4
                          Это подход кодера, а не программиста. Это как математик, который не помнит формул, но может их всегда вывести.
                          • +2
                            Кажется наоборот, который помнит, но не понимает откуда эти формулы.
                            • –1
                              Ну то есть, вы мне сейчас сможете рассказать, начиная с физических принципов, как это всё хранится, какое значение имеет выравнивание, как данные могут оказаться в свопе, как работает DMA в защищённом режиме и так далее?

                              Я, кстати, это рассказать смогу. Но как устроен zval мне не интересно.
                      • 0
                        Не так давно на Хабре была статья про неумение сборщика мусора работать с циклическими ссылками. Я так понимаю, это исправили начиная с какой-то версии?
                      • –10
                        Если у меня еще раз кто-нибудь спросит, почему я не люблю php, я просто покажу первый пример кода из этой статьи.
                        • +3
                          и чего с ним не так?
                          • +3
                            В других языках, наверняка, нюансов не меньше. Их надо знать, а лучше понимать. И PHP тут не причем.
                          • 0
                            Интересно
                            • 0
                              Если кому интересна данная тема, то есть хорошая книжка (с переводом на русский) «Профессиональное программирование на PHP» Джордж Шлосснейгл. Часть материала — как раз по начинке php.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.