LoginSignup
11
4

More than 5 years have passed since last update.

zend_stringに関するメモ

Last updated at Posted at 2017-01-05

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を呼び出したのち、strmemcpyでコピーしています。また、zend_stringval[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_stringzvalの値にします。

対象の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)
11
4
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4