В данный момент я упростил задачу еще и тем, что заведо считаю, что все принятые элементы массива — числа. Исходники расширений 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)
wispoz
21.06.2016 09:35На php я думаю было бы так:
$array = [1,2,3];
$number = array_sum($array);
$count = count($array);
Возможно и в исходниках подобное есть.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(); }
akamajoris
21.06.2016 10:26Когда писал расширения помогал хороший бложек: https://adobkin.com/. Там много информации про устройство зенда и плюшек PHP. Возможно кому-то будет полезно.
POPSuL
22.06.2016 16:30За предыдущую статью честно хотел поставить минус, но не успел. А вот за эту — плюсанул.
Одно смутило:
Для наглядности приведу из справочника все макросы:
Из какого собственно справочника?
P.S.
Мне нужно портировать свой маленький экстеншн с 5.6 на 7, и я запнулся именно на массивах, и в т.ч. на аргументах, которые передаются по ссылке. Если будете продолжать — напишите пожалуйста про аргументы, которые передаются по ссылке :)bizzonaru
22.06.2016 16:49+1Изучаю исходники PHP просто ради спортивного интереса.
https://wiki.php.net/phpng-upgrading
А что за расширение писали?
POPSuL
22.06.2016 17:01Огромное спасибо!
Расширение писал для ускорения кода для "внутренних нужд". Различные нативные альтернативы фолбэкам на чистом PHP, например, получение значения многомерного массива по ключу вида firstLevel.secondLevel.thirdLevel (
[firstLevel => [secondLevel => [thirdLevel => someValue]]])
. Ну и куча прочих функций для "микооптимизаций".
GrizliK1988
Спасибо за статью.
А какими ресурсами вы пользовались для поиска какой макрос что делает или какой макрос нужен, чтобы выполнить нужное действие?
Тоже в свободное время «копаю» исходники php, и, зачастую, тратится больше всего времени именно на поиск макроса.
bizzonaru
Просто смотрю расширения и потом если вижу что-то подходящее, то делаю grep по всему исходнику.
GrizliK1988
Значит легкого пути таки нет)