Продолжая своё поверхностное изучение исходников 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)


  1. GrizliK1988
    21.06.2016 09:24

    Спасибо за статью.

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

    Тоже в свободное время «копаю» исходники php, и, зачастую, тратится больше всего времени именно на поиск макроса.


    1. bizzonaru
      21.06.2016 09:36

      Просто смотрю расширения и потом если вижу что-то подходящее, то делаю grep по всему исходнику.


      1. GrizliK1988
        21.06.2016 09:40
        +1

        Значит легкого пути таки нет)


  1. wispoz
    21.06.2016 09:35

    На php я думаю было бы так:
    $array = [1,2,3];
    $number = array_sum($array);
    $count = count($array);
    Возможно и в исходниках подобное есть.


    1. bizzonaru
      21.06.2016 09:46
      +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();
      }
      
      


  1. serg_p
    21.06.2016 09:48

    если можно, поправьте тэги к посту. плиз :)


  1. akamajoris
    21.06.2016 10:26

    Когда писал расширения помогал хороший бложек: https://adobkin.com/. Там много информации про устройство зенда и плюшек PHP. Возможно кому-то будет полезно.


  1. POPSuL
    22.06.2016 16:30

    За предыдущую статью честно хотел поставить минус, но не успел. А вот за эту — плюсанул.
    Одно смутило:

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


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

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


    1. bizzonaru
      22.06.2016 16:49
      +1

      Изучаю исходники PHP просто ради спортивного интереса.

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

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


      1. POPSuL
        22.06.2016 17:01

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


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