PHP本体を改造したりPHP拡張モジュールを書くとき用のメモです。Zend/zend_string.h
を見ればわかる内容ばかりですが、備忘録としてまとめておきます。
zend_string
の概要
zend_string
はPHP 7から導入された内部的な文字列表現で、それ自体で参照カウンタを持っています。
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
val[1]
は可変長を実現するためのトリックで、実際にはlen+1
バイトを確保して使います。val
は必ずヌルターミネートされます。zend_string
解放時にval
がヌルターミネートされていないと警告が出ます。
メンバ変数へのアクセス用マクロ
char *ZSTR_VAL(zend_string *s)
文字列(val
メンバ)を取り出します。
size_t ZSTR_LEN(zend_string *s)
文字列長(len
メンバ)を取り出します。
メモリ確保
zend_string *zend_string_alloc(size_t len, int persistent)
文字列長len
文字のzend_string
構造体を動的メモリ確保および初期化します。
val
メンバに対応するlen+1
バイト(+1
はヌル文字の分です)およびzend_string
構造体のその他メンバに対応するメモリを動的に確保し、参照カウントを1にするなどして初期化済みのzend_string
を返します。
zend_string *zend_string_init(const char *str, size_t len, int persistent)
文字列長len
文字のzend_string
構造体をメモリ確保および初期化し、char *
で渡された文字列の値をzend_string
の値としてコピーします。
内部的にはzend_string_alloc
を呼び出したのち、str
をmemcpy
でコピーしています。また、zend_string
のval[len]
にヌル文字を代入します(つまりstr
がヌルターミネートされていなくても問題ありません)。
メモリ再確保
zend_string *zend_string_extend(zend_string *s, size_t len, int persistent)
zend_string
の文字列長をlen
文字に拡張します。今の文字列長より小さいlen
を指定した場合はアサーションにより実行が停止されます(PHPがデバッグビルドの場合のみ)。
zend_string *zend_string_truncate(zend_string *s, size_t len, int persistent)
zend_string
の文字列長をlen
文字に縮小します。今の文字列長より小さいlen
を指定した場合はアサーションにより実行が停止されます(PHPがデバッグビルドの場合のみ)。
zend_string *zend_string_realloc(zend_string *s, size_t len, int persistent)
zend_string
の文字列長をlen
文字に変更します。バグの防止と保守性のため、理由がなければ上記extend
およびtruncate
を使うべきでしょう。
メモリ開放
void zend_string_release(zend_string *s)
参照カウントを1減らします。参照カウントが0になったらメモリ解放を行います。
void zend_string_free(zend_string *s)
参照カウントがもともと1である前提でメモリ解放を行います。参照カウントが2以上だった場合はエラーとなります。
テンポラリの変数など、自分以外が参照していないことを保証したい場合に使う用途でしょう。
zend_string
からzval
への値のコピー
void ZVAL_STR_COPY(zval *z, zend_string *s)
zend_string
がinternedでない場合に参照カウントを1増やしてzval
の値にします。
通常はこちらを使うべきでしょう。
void ZVAL_NEW_STR(zval *z, zend_string *s)
参照カウンタを操作せずにzend_string
をzval
の値にします。
対象のzend_string
が自分で確保したばかりで他から参照されていない場合に使う用途でしょう。
文字列比較
zend_bool zend_string_equals(zend_string *s1, zend_string *s2)
zend_string
同士の値が同じであればtrue
を返します。
zend_bool zend_string_equals_literal(zend_string *str, const char *literal)
zend_string
の値とchar *
で渡された文字列リテラルの値が同じであればtrue
を返します。
文字列連結
文字列連結のAPIは提供されていないようで、PHPソースコード中に下記のようなコードが散見されます。
str = zend_string_alloc(ZSTR_LEN(s1) + ZSTR_LEN(s2), 0);
memcpy(ZSTR_VAL(str), ZSTR_VAL(s1), ZSTR_LEN(s1));
memcpy(ZSTR_VAL(str) + ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2)+1);
2つめのmemcpy
で第三引数に+1
しているのがポイントで、これを忘れるとヌルターミネートされずに怒られてしまいます。
文字および文字列の検索
こちらもPHP 7.1.0時点ではzend_string
に特化したAPIは提供されていません。下記のような方法で検索するしかなさそうです。
if (memchr(ZSTR_VAL(s), '/', ZSTR_LEN(s))) {
...
}
他に下記のAPIが利用できそうです。
const void *zend_memrchr(const void *s, int c, size_t n)
const char *zend_memnstr(const char *haystack, const char *needle, size_t needle_len, const char *end)
const char *zend_memnrstr(const char *haystack, const char *needle, size_t needle_len, char *end)