Специализированные функции вывода в PHP

Все мы знаем функцию printf() PHP и ее семейство. Эти функции полезны, но иногда они не обеспечивают достаточной функциональности. Кроме этого, добавление форматирующих строк в printf() не безопасно.

PHP добавляет свои собственные функции, подобные printf, для замены libc. В основном они добавляют новые форматы, используют zend_string вместо char * и так далее. Давайте рассмотрим их вместе.

Примечание

Эти функции добавлены для замены libc. Если вы используете sprintf (), например, вызвана будет не sprintf() библиотеки libc, а замещающая функция PHP. Кроме традиционной printf() все остальное заменяется.

Традиционное использование

Вы не должны использовать sprintf (), так как эта функция не выполняет никаких проверок и вызывает множество ошибок переполнения буфера.

Вы знаете размер буфера результата

Если вы знаете размер буфера, snprintf() или slprintf() выполнят работу за вас. Эти функции отличаются тем, что возвращают, но не тем, что они делают.

Обе функции выполняют вывод в соответствии с переданными форматами, и обе завершают буфер с помощью NUL-байта », что бы ни случилось. Но snprintf() возвращает количество символов, которые могли быть использованы. slprintf () возвращает количество символов, которые были использованы, что позволяет обнаруживать слишком маленькие буферы и усечение строк без учета конечного символа ».

Вот пример, из которого становится понятно printf sprintf PHP отличие:

char foo[8]; /* большой буфер из 8 символов */
const char str[] = "Hello world"; /* 12 символов включая  */
int r;
r = snprintf(foo, sizeof(foo), "%s", str);
/* r = 11 здесь, даже если только 7 печатных символов были записаны в foo */
/* Значение foo теперь 'H' 'e' 'l' 'l' 'o' ' ' 'w' '' */

Функция snprintf () не надежна при использовании, поскольку не позволяет обнаружить возможное усечение строки.
Как видно из приведенного выше примера, «Hello world 0» не помещается в восьмибайтовый буфер. Это очевидно, но snprintf () все равно возвращает 11, что является strlen(«Hello world»). Таким образом, вы не сможете обнаружить, что строка усечена.

Вот пример использования slprintf() вместо printf PHP:

char foo[8]; /* большой буфер из 8 символов */
const char str[] = "Hello world"; /* 12 символов включая  */
int r;
r = slprintf(foo, sizeof(foo), "%s", str);
/* r = 7 здесь, потому что 7 печатных символов были записаны в  foo */
/* Значение foo теперь 'H' 'e' 'l' 'l' 'o' ' ' 'w' '' */

При использовании функции slprintf () буфер результата foo содержит ту же самую строку, но возвращаемое значение теперь равно 7. Это меньше, чем 11 символов из строки «Hello world», поэтому можно обнаружить, что она была усечена:

if (slprintf(foo, sizeof(foo), "%s", str) < strlen(str)) {
    /* Произошло усечение строки */
}

Помните:

  • Эти две функции всегда завершают строку с NULL, с усечением или без. Конечные стоки являются безопасными строками C;
  • Только slprintf () позволяет обнаружить усечение строк.

Эти две функции определены в main/snprintf.c

Вы не знаете размер буфера

Если не знаете размер конечного буфера, тогда вам нужен буфер, выделяемый динамически, и затем необходимо использовать функцию sprintf (). Помните, что вам придется освобождать буфер самостоятельно!

Вот пример:

#include <time.h>
char *result;
int r;
time_t timestamp = time(NULL);
r = spprintf(&result, 0, "Here is the date: %s", asctime(localtime(&timestamp)));
/* теперь используем результат, который содержит что-то вроде "Here is the date: Thu Jun 15 19:12:51 2017n" */
efree(result);

Функция spprintf (в отличие от PHP printf) возвращает количество символов, которые были помещены в конечный буфер, не считая финального символа «», поэтому вы знаете количество выделенных байтов (минус один).

Обратите внимание, что выделение памяти выполняется с помощью ZendMM (выделение по запросу) и поэтому должно использоваться как часть запроса и освобождаться с помощью efree(), а не free().

Если хотите ограничить размер буфера, то вы передаете это ограничение в качестве второго аргумента. Если вы передадите 0, то лимита нет:

#include <time.h>
char *result;
int r;
time_t timestamp = time(NULL);
/* Не выводите больше 10 байт или выделяйте больше чем 11 байт*/
r = spprintf(&result, 10, "Here is the date: %s", asctime(localtime(&timestamp)));
/* r == 10 здесь и 11 байт были выделены под результат*/
efree(result);

Примечание

Старайтесь не использовать динамическое распределение памяти. Оно влияет на производительность. Лучше используйте буфер, выделенный из статического стека.

Функция spprintf () находится в main/spprintf.c.

А что насчет printf()?

Если нужна функция printf PHP, или другими словами, отформатированный вывод в поток, используйте php_printf().

Эта функция используется внутри spprintf() и выполняет динамическое выделение памяти, которую освобождает сразу после отправки на выход SAPI, stdout в случае CLI, или в выходной буфер (буфер CGI, например) для других SAPI.

Специальные форматы printf в PHP

Помните, что PHP заменяет большинство функций printf () библиотеки libc своими собственными реализациями. Можно посмотреть API анализа аргументов, который легко понять, прочитав исходный код.

Это означает, что алгоритм анализа аргументов был полностью переписан и может отличаться от того, к которому вы привыкли в libc. Например, языковая настройка libc учитывается в большинстве случаев.

Могут использоваться специальные форматы, например «%I64», для прямой печати в int64 или «%I32». Также можно использовать «%Z», чтобы сделать zval печатаемым (в соответствии с правилами приведения PHP к строкам), что является отличным дополнением.

Средство форматирования также распознает бесконечные числа и напечатает «INF» или «NAN» для нечислового значения.

Если вы допустите ошибку и попытаетесь вывести указатель NULL (в этом случае libc аварийно завершилась бы), то PHP вернет null в качестве результирующей строки.

Примечание

Если в PHP printf вы видите волшебное null, это означает, что вы передали NULL-указатель в одну из функций семейства printf.

Печать в zend_string

Поскольку zend_string –распространенная структура в PHP-коде, вам может понадобиться вывод в zend_string вместо традиционного массива C char *. Для этого используйте функцию strpprintf().

API функции: zend_string * strpprintf (size_t max_len, const char * format, …). Это значит, что возвращается zend_string, а не количество печатных символов. Можно ограничить это число, используя первый параметр (передача значения 0 означает бесконечное число). При этом необходимо помнить, что zend_string будет выделен с помощью Zend Memory Manager и, следовательно, привязан к текущему запросу.

Очевидно, что API используется совместно с тем, который был приведен выше.

Вот пример:

zend_string *result;
result = strpprintf(0, "You are using PHP %s", PHP_VERSION);
/* Делаем что-то с результатом */
zend_string_release(result);

Примечание по zend_ API

Вы можете встретить функции zend_spprintf () или zend_strpprintf (). Это то же самое, что было описано выше. Просто в статье они приведены как часть разделения между Zend Engine и PHP Core.

Перевод статьи «PHP’s custom printf functions» был подготовлен дружной командой проекта Сайтостроение от А до Я.