zend_stringに関するメモ

PHP本体を改造したりPHP拡張モジュールを書くとき用のメモです。`Zend/zend_string.h`を見ればわかる内容ばかりですが、備忘録としてまとめておきます。

# `zend_string`の概要

`zend_string`はPHP 7から導入された内部的な文字列表現で、それ自体で参照カウンタを持っています。

```c
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ソースコード中に下記のようなコードが散見されます。

```c
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は提供されていません。下記のような方法で検索するしかなさそうです。

```c
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)`