Any program needs to manage strings. Here, we’ll detail a custom solution that fits PHP needs : zend_string. Every time PHP needs to work with a string, a zend_string structure will be used. This structure is just a simple thin wrapper over the char * string type of the C language.
It adds memory management facilities, so that a same string can be shared in several places without the need to duplicate it. Also, some strings are “interned”, that is they are “persistent” allocated and specially managed by the memory manager so that they don’t get destroyed across several requests. Those later get a permanent allocation from Zend Memory Manager.
Like you can see, the structure embeds a zend_refcounted_h header. This is done for memory management and reference. As the string is very likely to be used as the key of a HashTable probe, it embeds its hash in the h field. This is an unsigned long zend_ulong. This number is only used when the zend_string needs to be hashed, especially when used together with HashTables: zend_array; this is very likely though.
As you know, the string knows its length as the len field, to support “binary strings”. Binary strings are strings that embed one or several NUL characters (\0). When passed to libc functions, those strings will get truncated or their length won’t be computed the right way. So in zend_string, the length of the string is always known. Please, note that the length computes the number of ASCII chars (bytes) not counting the terminating NUL, but counting the eventual middle NULs. For example, the string “foo” is stored as “foo\0” in a zend_string and its length is then 3. Also, the string “foo\0bar” will be stored as “foo\0bar\0” and the length will be 7.
Finally, the characters are stored into the char field. This is not a char *, but a char. Why that? This is a memory optimization known as “C struct hack” (you may use a search engine with these terms). Basically, that allows the engine to allocate space for the zend_string structure and the characters to be stored, as one solo C pointer. This optimizes memory accesses as memory will be a contiguous allocated block, and not two blocks sparsed in memory (one for zend_string *, and one for the char * to store into it).
This struct hack must be remembered, as the memory layout looks like with the C chars at the end of the C zend_string structure, and may be felt/seen when using a C debugger (or when debugging strings). This hack is entirely managed by the API you’ll use when manipulating zend_string structures.
The above simple example show you basic string management. The zend_string_init() function (which in fact is a macro, but let’s pass such details) should be given your full C string as a char *, and its length. The last parameter- of type int- should be 0 or 1. If you pass 0, you ask the engine to use a request-bound heap allocation using the Zend Memory Manager. Such allocation will be destroyed at the end of the current request. If you don’t do it yourself, on a debug build, the engine will shout at you about a memory leak you just created. If you pass 1, you ask for what we called a “persistent” allocation, that is the engine will use a traditional C malloc() call and will not track the memory allocation in any way.
If you need more information about memory management, you may read the dedicated chapter.
Then, we display the string. We access the character array by using the ZSTR_VAL() macro. ZSTR_LEN() allows access to the length information. zend_string related macros all start with ZSTR_**(), beware that is not the same as Z_STR**() macros.
Finally, we release the string using zend_string_release(). This release is mandatory. This is about memory management. The “releasing” is a simple operation : decrement the reference counter of the string, if it falls to zero, the API will free the string for you. If you forget to release a string, you will very likely create a memory leak.
You must always think about memory management in C. If you allocate - whether directly using malloc(), or using an API that will do it for you - you must free() at some point. Failing to do that will create memory leaks and translate into a badly designed program that nobody will be able to use safely.
One very nice feature of zend_string API is that it allows one part to “own” a string by simply declaring interest with it. The engine will then not duplicate the string in memory, but simply increment its refcount (as part of its zend_refcounted_h). This allows sharing a single piece of memory in many places into the code.
/* creates bar2 and shares the "bar" string from bar into bar2.
We start by just allocating “foo” and “bar”. Then we create the bar2 string as being a copy of bar. Here, everybody must remember : bar and bar2 point to the same C string in memory, and changing one will change the second one. This is zend_string_copy() behavior : it just increments the refcount of the owned C string.
If we want to separate the strings- aka we want to have two different copies of that string in memory -we need to duplicate using zend_string_dup(). We then duplicate bar2 variable string into the baz variable. Now, the baz variable embeds its own copy of the string, and can change it without impacting bar2. That is what we do : we change the final ‘r’ in ‘bar’ with a ‘z’, for ‘baz’. And then we display it, and free memory of every string.
Note that we forgot the hash value (if it were computed before, no need to think about that detail). This is a good practice to remember about. Like we already said, the hash is used if the zend_string is used as part of HashTables. This is a very common operation in development, and changing a string value requires to recompute the hash value as well. Forgetting such a step will lead to bugs that could cost some time to track.
Now that you know how to manage and manipulate zend_string, let’s see the interaction they got with the zval container.
You need to be familiar with zvals, if not, read the Zvals dedicated chapter.
What you must memorize is that every macro beginning by ZSTR_***(s) will act on a zend_string.
A few other that you won’t probably need also exist.
Just a quick note about classical C strings. In C, strings are character arrays (char foo), or pointers to characters (char *). They don’t know anything about their length, that’s why they are NUL terminated (knowing the beginning of the string and its end, you know its length).
Before PHP 7, zend_string structure simply did not exist. A traditional char * / int couple were used back in that time. You may still find rare places into PHP source where char * / int couple is used instead of zend_string. You may also find API facilities to interact between a zend_string on one side, and a char * / int couple on the other side.
Wherever it is possible : make use of zend_string. Some rare places don’t make use of zend_string because it is not relevant at that place to use them, but you’ll find lots of reference to zend_string anyway in PHP source code.
Just a quick word here about interned strings. You should rarely need such a concept in extension development. Interned strings also interact with OPCache extension.
Interned strings are deduplicated strings. When used with OPCache, they also get recycled from request to request.
But a question arises : Hasn’t that piece of string already been created before you need it? When you need a string, you code is executed at some point in PHP’s life, that means that some piece of code happening before yours may have needed the exact same piece of string (“foo” for our example).
Interned strings is about asking the engine to probe the interned strings store, and reuse the already allocated pointer if it could find your string. If not : create a new string and “intern” it, that is make it available to other parts of PHP source code (other extensions, the engine itself, etc…).
What we do in the code above, is we create a new zend_string very classicaly. Then, we pass that created zend_string to zend_new_interned_string(). This function looks for the same piece of string (“foo” here) into the engine interned string buffer. If it finds it (meaning someone already created such a string), it then releases your string (probably freeing it) and replaces it by the string from the interned string buffer. If it does not find it: it adds it to the interned string buffer and so makes it available for future usage or other parts of PHP.
You must take care about memory allocation. Interned strings always have a refcount set to one, because they don’t need to be refcounted, as they will get shared with the interned string buffer, and thus they can’t be destroyed out of it.
It’s all about garbage collection.
When a string is interned, its GC flags are changed to add the IS_STR_INTERNED flag, whatever the memory allocation class they use (permanent or request based). This flag is probed when you want to copy or release a string. If the string is interned, the engine does not increment its refcount as you copy the string. But it doesn’t decrement it nor free it if you release the string. It shadowly does nothing. At the end of the process lifetime, it will destroy its interned string buffer, and it will free your interned strings.
This process is in fact a little bit more complex than this if OPCache fires in. OPCache extension changes the way interned strings are used. Without OPCache, if you create an interned zend_string during the process of a request, that string will get cleared at the end of the current request and won’t be reused for the next request. However, if you use OPCache, interned strings are stored into a shared memory segment and shared between every PHP process of the same pool. Also, interned strings get reused across several requests.
If OPCache is used (it should be), and if you need to create request-bound read-only strings : use an interned string.
If you need a string you know for sure PHP will have interned (a well-known-PHP-string, f.e “php” or “str_replace”), use an interned string.
If the string is not read-only and could/should be altered after its been created, do not use an interned string.
If the string is unlikely to be reused in the future, do not use an interned string.