str_repeat関数、使っていますか？

文字列を繰り返して出力したいときってありますよね？
例えば・・・

```php
for ($i = 0; $i < 10; $i++) {
  echo "ｗ";
}
// --> "ｗｗｗｗｗｗｗｗｗｗ"
```

でも、`str_repeat` 関数（[PHP マニュアル](http://php.net/manual/ja/function.str-repeat.php)）を使えば、もっとシンプルに書けますよ。

```php
echo str_repeat("ｗ", 10);
// --> "ｗｗｗｗｗｗｗｗｗｗ"
```

# 文字ではなく文字列
なので、正月早々、友人[^1]に爆弾投下したくなったときにも使えます。

[^1]: 友人がいれば、の話しだが！

```php
echo str_repeat("あけおめ〜", 10);
// --> "あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜あけおめ〜"
```

# マルチバイト文字いけますよ
関数名が「mb_*」でないからマルチバイト文字いけないとか、そういうわけではありません。

# 暇だったのでソース読んでみた
これだけだったらおもしろくないと思うので、PHP のソースコードを眺めてみました。初めて眺めたので詳しい仕組みは理解できていないし、最近触れてないのでC言語の理解度も怪しいけど、だいたいこんな感じかなと。

リポジトリを検索すると、`php-src/ext/standard/string.c` に文字列関数が定義されているようです。その [5387 行目](https://github.com/php/php-src/blob/PHP-7.3.0/ext/standard/string.c#L5387) を見ると、`str_repeat` 関数が定義されていました。

てっきり内部の実装も `for` 文でループしているのかと思いきや（そんな素人的な発想で作るわけないよね）、`memset` や `memcpy`、`memmove` 関数を使っています。

例えば、文字列長が 1 バイトならば、`memset` 関数で、チョチョイと入れちゃう。

```c
/* Heavy optimization for situations where input string is 1 byte long */
if (ZSTR_LEN(input_str) == 1) {
	memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult);
}
```

文字列長が 1 バイトよりも長かった場合は、ちょっと複雑なことをしています。

```c
const char *s, *ee;
char *e;
ptrdiff_t l=0;
memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str));
s = ZSTR_VAL(result);
e = ZSTR_VAL(result) + ZSTR_LEN(input_str);
ee = ZSTR_VAL(result) + result_len;

while (e<ee) {
	l = (e-s) < (ee-e) ? (e-s) : (ee-e);
	memmove(e, s, l);
	e += l;
}
```

初見では何をやっているのかよく分からなかったのですが（とくに `while` 文）、たぶんこういうことだろうと。なお、入力された文字列を `"abc"`、回数を `3` とします。

```c
memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str));
```

まず、`memcpy` 関数で、入力文字列 `input_str` を `result` にコピーします。
このときのメモリー上の様子は以下のとおり。括弧内は、繰り返しが完了したあとのイメージです。

| アドレス |    0     |  1  |  2  |    3     |   4   |   5   |   6   |   7   |   8   |     9     |
| ------ | -------- | --- | --- | ------- | ----- | ----- | ----- | ----- | ----- | --------- |
| result | a        | b   | c   | ( a )    | ( b ) | ( c ) | ( a ) | ( b ) | ( c ) |           |
|        | ↑変数`s` |     |     | ↑変数`e` |       |       |       |       |       | ↑変数`ee` |

ポインタ変数 `s` は、結果文字列の先頭を指していますが、`memcpy` 関数で先頭に入力文字列をコピーしたので、実質的には入力文字列の先頭を指し示しています。

ポインタ変数 `e` は、ループの中で文字列を追加する位置を指し示すのに使われています。最初は入力文字列の長さだけ移動した位置を指し示していますが、ループの中で移動していることに注意です。

ポインタ変数 `ee` は、文字列を回数分繰り返したあとのポインタの位置を指し示しています（こういうのを番兵と言うんですかね？）。

```c
while (e<ee) {
	l = (e-s) < (ee-e) ? (e-s) : (ee-e);
	memmove(e, s, l);
	e += l;
}
```

ここが実際に文字列を追加していっているところです。変数 `l` の求め方が「なんでこんな面倒なことしてるの？」と思いましたが、要するに、これって、入力文字列の長さを求めているだけなんですよね・・・

1 回目の繰り返しでは、`l = (3-0) < (9-3) ? (3-0) : (9-3)` なので、`l = 3` となります。`memmove` 関数で先頭から 3 バイトを、変数 `e` が指し示しているアドレスにコピーし、変数 `e` が指し示す位置を移動します。

| アドレス |    0     |  1  |  2  |  3  |  4  |  5  |    6     |   7   |   8   |     9     |
| ------ | -------- | --- | --- | --- | --- | --- | -------- | ----- | ----- | --------- |
| result | a        | b   | c   | a   | b   | c   | ( a )    | ( b ) | ( c ) |           |
|        | ↑変数`s` |     |     |     |     |     | ↑変数`e` |       |       | ↑変数`ee` |

2 回目の繰り返しでは、`l = (6-0) < (9-6) ? (6-0) : (9-6)` なので、`l = 3` となります。`memmove` 関数で先頭から 3 バイトを、変数 `e` が指し示しているアドレスにコピーし、変数 `e` が指し示す位置を移動します。

| アドレス |    0     |  1  |  2  |  3  |  4  |  5  |   6   |   7   |   8   |      9       |
| ------ | -------- | --- | --- | --- | --- | --- | ----- | ----- | ----- | ------------ |
| result | a        | b   | c   | a   | b   | c   | ( a ) | ( b ) | ( c ) |              |
|        | ↑変数`s` |     |     |     |     |     |       |       |       | ↑変数`ee, e` |

変数 `e` の指し示す位置が変数 `ee` と同じになり、ループ条件 `e<ee` を満たさないため、ループ終了となります。
そして、文字列の最後に終端文字 `\0` を追加して、呼び出し元に返却します。

# まとめ
PHP マニュアルで時々どんな関数があるかを眺めてみることも大事ですね。まる。