Skip to content

Commit 7f8c1d4

Browse files
author
Julien Pauli
committed
printing functions chapter
1 parent a924b0c commit 7f8c1d4

File tree

1 file changed

+199
-4
lines changed

1 file changed

+199
-4
lines changed
Lines changed: 199 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,200 @@
1-
Printing functions
2-
==================
1+
PHP's custom printf functions
2+
=============================
33

4-
You all know libc's ``printf()``. This chapter will detail those many clones PHP declares and use, what's their goal,
5-
why use them and when to use them.
4+
You all know libc's ``printf()`` and family. This chapter will detail those many clones PHP declares and use, what's
5+
their goal, why use them and when to use them.
6+
7+
.. note:: Libc's documentation about ``printf()`` and friends
8+
`is located here <https://www.gnu.org/software/libc/manual/html_node/Formatted-Output-Functions.html>`_
9+
10+
You know that those functions are useful, but sometimes don't provide enough functionalities.
11+
Also, you know that
12+
`adding format strings <https://www.gnu.org/software/libc/manual/html_node/Customizing-Printf.html>`_ to ``printf()``
13+
family is not trivial, not portable and security risky.
14+
15+
PHP adds its own printf-like functions to replace libc ones and to be used by the internal developer.
16+
They will mainly add new formats, play with :doc:`zend_string<zend_strings>` instead of
17+
``char *``, etc... Let's see them together.
18+
19+
.. warning:: You must master your libc default ``printf()`` formats. Read
20+
`their documentation here <http://www.cplusplus.com/reference/cstdio/printf/>`_.
21+
22+
.. note:: Those functions are added **to replace** libc ones, that means that if you use ``sprintf()`` f.e, that won't
23+
lead to libc's ``sprintf()``, but to PHP replacement. Except the traditionnal ``printf()``, everything else
24+
is replaced.
25+
26+
Traditionnal use
27+
****************
28+
29+
First of all, you should not use ``sprintf()``, as that function doesn't perform any check and allows many buffer
30+
overflow errors. Please, try to avoid using it.
31+
32+
.. warning:: Please try to avoid using ``sprintf()`` as much as possible.
33+
34+
Then, you have some choice.
35+
36+
You know your result buffer size
37+
--------------------------------
38+
39+
If you know your buffer size, ``snprintf()`` or ``slprintf()`` will do the job for you. There is a difference in what
40+
those functions return, but not in what those functions do.
41+
42+
They both print according to the formats passed, and they both terminate your buffer by a ``NUL`` byte *'\\0'* whatever
43+
happens. However, ``snprintf()`` returns the number of characters that could have been used, whereas ``slprintf()``
44+
returns the number of characters that have effectively been used, thus enabling to detect too-small buffers and string
45+
truncation. This, is not counting the final *'\\0'*.
46+
47+
Here is an example so that you fully understand::
48+
49+
char foo[8]; /* 8-char large buffer */
50+
const char str[] = "Hello world"; /* 12 chars including \0 in count */
51+
int r;
52+
53+
r = snprintf(foo, sizeof(foo), "%s", str);
54+
/* r = 11 here even if only 7 printable chars were written in foo */
55+
56+
/* foo value is now 'H' 'e' 'l' 'l' 'o' ' ' 'w' '\0' */
57+
58+
``snprintf()`` is not a good function to use, as it does not allows to detect an eventual string truncation.
59+
As you can see from the example above, "Hello world\\0" doesn't fit in an eight-byte buffer, that's obvious, but
60+
``snprintf()`` still returns you 11, which is ``strlen("Hello world\0")``. You have no way to detect that the string's
61+
got truncated.
62+
63+
Here is ``slprintf()``::
64+
65+
char foo[8]; /* 8-char large buffer */
66+
const char str[] = "Hello world"; /* 12 chars including \0 in count */
67+
int r;
68+
69+
r = slprintf(foo, sizeof(foo), "%s", str);
70+
/* r = 7 here , because 7 printable chars were written in foo */
71+
72+
/* foo value is now 'H' 'e' 'l' 'l' 'o' ' ' 'w' '\0' */
73+
74+
With ``slprintf()``, the result buffer ``foo`` contains the exact same string, but the returned value is now 7. 7 is
75+
less than the 11 chars from the *"Hello world"* string, thus you can detect that it got truncated::
76+
77+
if (slprintf(foo, sizeof(foo), "%s", str) < strlen(str)) {
78+
/* A string truncation occured */
79+
}
80+
81+
Remember:
82+
83+
* Those two function always ``NUL`` terminate the string, truncation or not. Result strings are then safe C strings.
84+
* Only ``slprintf()`` allows to detect a string truncation.
85+
86+
Those two functions are defined in
87+
`main/snprintf.c <https://github.com/php/php-src/blob/648be8600ff89e1b0e4a4ad25cebad42b53bed6d/main/snprintf.c>`_
88+
89+
You don't know your buffer size
90+
-------------------------------
91+
92+
Now if you don't know your result buffer size, you need a dynamicaly allocated one, and then you'll use ``spprintf()``.
93+
Remember that **you'll have to free** the buffer by yourself !
94+
95+
Here is an example::
96+
97+
#include <time.h>
98+
99+
char *result;
100+
int r;
101+
102+
time_t timestamp = time(NULL);
103+
104+
r = spprintf(&result, 0, "Here is the date: %s", asctime(localtime(&timestamp)));
105+
106+
/* now use result that contains something like "Here is the date: Thu Jun 15 19:12:51 2017\n" */
107+
108+
efree(result);
109+
110+
``spprintf()`` returns the number of characters that've been printed into the result buffer, not counting the final
111+
*'\\0'*, hence you know the number of bytes that got allocated for you (minus one).
112+
113+
Please, note that the allocation is done using ZendMM (request allocation), and should thus be used as part of a
114+
request and freed using ``efree()`` and not ``free()``.
115+
116+
.. note:: :doc:`The chapter about Zend Memory Manager <../../memory_management/zend_memory_manager>` (ZendMM) details
117+
how dynamic memory is allocated through PHP.
118+
119+
If you want to limit the buffer size, you pass that limit as the second argument, if you pass *0*, that means
120+
unlimited::
121+
122+
#include <time.h>
123+
124+
char *result;
125+
int r;
126+
127+
time_t timestamp = time(NULL);
128+
129+
/* Do not print more than 10 bytes || allocate more than 11 bytes */
130+
r = spprintf(&result, 10, "Here is the date: %s", asctime(localtime(&timestamp)));
131+
132+
/* r == 10 here, and 11 bytes were allocated into result */
133+
134+
efree(result);
135+
136+
.. note:: Whenever possible, try not to use dynamic memory allocations. That impacts performances. If you got the
137+
choice, go for the static stack allocated buffer.
138+
139+
``spprintf()`` is written in
140+
`main/spprintf.c <https://github.com/php/php-src/blob/648be8600ff89e1b0e4a4ad25cebad42b53bed6d/main/spprintf.c>`_.
141+
142+
What about printf() ?
143+
---------------------
144+
145+
If you need to ``printf()``, aka to print formatted to the output stream, use ``php_printf()``. That function
146+
internally uses ``spprintf()``, and thus performs a dynamic allocation that it frees itself just after having sent it
147+
to the SAPI output, aka stdout in case of CLI, or the output buffer (CGI buffer f.e) for other SAPIs.
148+
149+
Special PHP printf formats
150+
--------------------------
151+
152+
Remember that PHP replaces most libc's ``printf()`` functions by its own of its own design. You can have a look at
153+
the argument parsing API which is easy to understand `from reading the source
154+
<https://github.com/php/php-src/blob/509f5097ab0b578adc311c720afcea8de266aadd/main/spprintf.c#L203>`_.
155+
156+
What that means is that arguments parsing algo has been fully rewritten, and may differ from what you're used to in libc.
157+
F.e, the libc locale is note taken care of in most cases.
158+
159+
Special formats may be used, like *"%I64"* to explicitely print to an int64, or *"%I32"*.
160+
You can also use *"%Z"* to make a zval printable (according to PHP cast rules to string), that one is a great addition.
161+
162+
The formatter will also recognize infinite numbers and print "INF", or "NAN" for not-a-number.
163+
164+
If you make a mistake, and ask the formatter to print a ``NULL`` pointer, where libc will crash for sure, PHP will
165+
return *"(null)"* as a result string.
166+
167+
.. note:: If in a printf you see a magic *"(null)"* appearing, that means you passed a NULL pointer to one of PHP
168+
printf family functions.
169+
170+
171+
Printf()ing into zend_strings
172+
-----------------------------
173+
174+
As :doc:`zend_string <zend_strings>` are a very common structure into PHP source, you may need to ``printf()`` into a
175+
``zend_string`` instead of a traditionnal C ``char *``. For this, use ``strpprintf()``.
176+
177+
Tha API is ``zend_string *strpprintf(size_t max_len, const char *format, ...)`` that means that the ``zend_string`` is
178+
returned to you, and not the number of printed chars as you may expect. You can limit that number though, using the
179+
first parameter (pass 0 to mean infinite); and you must remember that the ``zend_string`` will be allocated using the
180+
Zend Memory Manager, and thus bound to the current request.
181+
182+
Obviously, the format API is shared with the one seen above.
183+
184+
Here is a quick example::
185+
186+
zend_string *result;
187+
188+
result = strpprintf(0, "You are using PHP %s", PHP_VERSION);
189+
190+
/* Do something with result */
191+
192+
zend_string_release(result);
193+
194+
A note on ``zend_`` API
195+
-----------------------
196+
197+
You may meet ``zend_spprintf()``, or ``zend_strpprintf()`` functions. Those are the exact same as the ones seen above.
198+
199+
They are just here as part of the separation between the Zend Engine and PHP Core, a detail that is not important for
200+
us, as into the source code, everything gets mixed together.

0 commit comments

Comments
 (0)