Макросы Zend обхода циклов (HashTable Iteration)

  • Tutorial
Продолжая своё поверхностное изучение исходников PHP (7.0.7) и написания простейшего расширения к нему, хотел бы в этот раз немного углубится и описать приемы обхода массива через принятый аргумент функции, с которыми я познакомился при реализации простой PHP функции median(). Задача этой функции проста — вернуть средне-арифметическое значение. Возможна данная публикация будет полезной другим разработчикам PHP, таким же как и я, которые решили в свободное время немного изучить архитектуру любимого языка, на котором зарабатывают деньги. В предыдущей публикации я на “скорую руку” описал прием быстрого создания расширения в PHP с реализаций функции расчета факториала. Она проста в той степени, что принимает простой параметр целого типа и затем рекурсивно вызывается. Реализация функции median() усложнена тем, что принимаемый параметр — массив, по нему нужно пройтись, для суммирования общего значения, а также просчитать общее число элементов в массиве.

В данный момент я упростил задачу еще и тем, что заведо считаю, что все принятые элементы массива — числа. Исходники расширений PHP удивительны тем, что здесь “все пишется” через использование макросов. По крайней мере создается такое первоначальное мнение. Оказывается, для прохода по списку элементов в массиве тоже используются макросы. Для наглядности приведу сразу код функции с последующим небольшим описанием.

Функция описана все в том же файле — mathstat.c расширения mathstat. Ссылка на github.

Занесение в список функций расширения mathstat:

const zend_function_entry mathstat_functions[] = {
        PHP_FE(confirm_mathstat_compiled,       NULL)           /* For testing, remove later. */
        PHP_FE(ms_factorial,    arginfo_ms_factorial)
        PHP_FE(ms_median,       NULL)
        PHP_FE_END      /* Must be the last line in mathstat_functions[] */
};


Само определение тела функции:

PHP_FUNCTION(ms_median)
{
   int argc = ZEND_NUM_ARGS();
   double total = 0;
   int count = 0;
   zval *array,
        *value;

   if (zend_parse_parameters(argc, "a", &array) == FAILURE) {
        RETURN_DOUBLE(0);
   }

   ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(array), value) {
        total = total + zval_get_double (value);
        count += 1;
   } ZEND_HASH_FOREACH_END();

   if (count == 0 || total == 0) {
        RETURN_DOUBLE(0);
   }

   RETURN_DOUBLE(total/count);
}


Если смотреть тело функции, то как и в прошлый раз, вызывается функция проверки параметра, где в качестве шаблона принимаемого типа аргумента задаем значение “a” (array)

   if (zend_parse_parameters(argc, "a", &array) == FAILURE) {
        RETURN_DOUBLE(number);
   }


Теперь самое интересное, проход по циклу реализован через макрос ZEND_HASH_FOREACH_VAL. Всего макросов которые проходят по массиву я нашел в справочках 7 штук. При этом, везде используется вместо массива термин HashTable. Для нашего случая я выбрал самый простой макрос. Первым аргументом он получает сам принятый массив через функцию, а вторым zval (базовая структура данных, которая хранить себе значение и тип данных — видео по этой части Дмитрия Стогова). В данном случае, я просто вызываю функцию zval_get_double, которая грубо говоря, мне и возвращает самое значение из массива. Если переписать это на обычный код PHP, то получится:

  1 <?php
  2   $array = [1,2,3];
  3
  4   $number = 0;
  5   $count = 0;
  6
  7   foreach($array as $val) {
  8      $number += $val;
  9      $count += 1;
 10   }
 11
 12   echo "cnt: ".$count." total: ".$number."\n";
 13 ?>


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

ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)


то без кода уже понятно, что это аналог php цикла:

foreach($array as $key => $value) {
} 


Для наглядности приведу из справочника все макросы:

ZEND_HASH_FOREACH_VAL(ht, val)
ZEND_HASH_FOREACH_KEY(ht, h, key)
ZEND_HASH_FOREACH_PTR(ht, ptr)
ZEND_HASH_FOREACH_NUM_KEY(ht, h)
ZEND_HASH_FOREACH_STR_KEY(ht, key)
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, key, val)
ZEND_HASH_FOREACH_KEY_VAL(ht, h, key, val)


На этом все. Спасибо за отнятое время и потерянные деньги на мобильном трафике.
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 10
  • 0
    Спасибо за статью.

    А какими ресурсами вы пользовались для поиска какой макрос что делает или какой макрос нужен, чтобы выполнить нужное действие?

    Тоже в свободное время «копаю» исходники php, и, зачастую, тратится больше всего времени именно на поиск макроса.
    • 0
      Просто смотрю расширения и потом если вижу что-то подходящее, то делаю grep по всему исходнику.
      • +1
        Значит легкого пути таки нет)
    • 0
      На php я думаю было бы так:
      $array = [1,2,3];
      $number = array_sum($array);
      $count = count($array);
      Возможно и в исходниках подобное есть.
      • +1
        Спасибо, что напомнили, для интереса посмотрел реализацию кода, грубо говоря тот же подход, за исключением, проверки типа данных и использования внутренней функции fast_add_function

        PHP_FUNCTION(array_sum)
        {
                zval *input,
                         *entry,
                         entry_n;
        
                if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &input) == FAILURE) {
                        return;
                }
        
                ZVAL_LONG(return_value, 0);
        
                ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(input), entry) {
                        if (Z_TYPE_P(entry) == IS_ARRAY || Z_TYPE_P(entry) == IS_OBJECT) {
                                continue;
                        }
                        ZVAL_COPY(&entry_n, entry);
                        convert_scalar_to_number(&entry_n);
                        fast_add_function(return_value, return_value, &entry_n);
                } ZEND_HASH_FOREACH_END();
        }
        
        
      • 0
        если можно, поправьте тэги к посту. плиз :)
        • 0
          Когда писал расширения помогал хороший бложек: https://adobkin.com/. Там много информации про устройство зенда и плюшек PHP. Возможно кому-то будет полезно.
          • 0
            За предыдущую статью честно хотел поставить минус, но не успел. А вот за эту — плюсанул.
            Одно смутило:
            Для наглядности приведу из справочника все макросы:


            Из какого собственно справочника?

            P.S.
            Мне нужно портировать свой маленький экстеншн с 5.6 на 7, и я запнулся именно на массивах, и в т.ч. на аргументах, которые передаются по ссылке. Если будете продолжать — напишите пожалуйста про аргументы, которые передаются по ссылке :)
            • +1
              Изучаю исходники PHP просто ради спортивного интереса.

              https://wiki.php.net/phpng-upgrading

              А что за расширение писали?

              • 0

                Огромное спасибо!


                Расширение писал для ускорения кода для "внутренних нужд". Различные нативные альтернативы фолбэкам на чистом PHP, например, получение значения многомерного массива по ключу вида firstLevel.secondLevel.thirdLevel ([firstLevel => [secondLevel => [thirdLevel => someValue]]]). Ну и куча прочих функций для "микооптимизаций".

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