基本的なメモリ管理

Zend エンジンのメモリ管理は、PHP のようなシステムにとって重要な機能として実装されています。 メモリの管理や最適化が実際にどのような動きになっているかは、ここでは取り上げません。 しかし、その機能をきちんと理解しておけば、このハッカー向けガイドの理解がさらに深まり、 PHP 全体で使われる用語や機能を把握できるでしょう。

ハッカーにとって最も重要な機能である、メモリ割り当ての追跡について最初に説明しましょう。 メモリ割り当てを追跡すれば、ハッカーの悩みの種であるメモリリークを回避できます。 PHP をデバッグモードで (--enable-debug をつけて) ビルドすると、 検出したメモリリークが逐一報告されるという、これまでにない完全な世界になります。

この機能は重要なものだしとても便利なものではありますが、 ハッカーたる者、それに甘えてはいけません。 コードをデプロイする前にメモリリークは解決しておくようにしましょう。 SAPI 環境でのメモリリークは、あっという間に深刻な問題を引き起こします。

もう一つ、副次的な機能ではあるけれども注目すべきものがあります。 それは、メモリマネージャーが、PHP のインスタンスごとのメモリ使用量のハードリミットを決める一因となっていることです。 ご存じのとおり、無限に使えるものなんてどこにもありません。 何かのコードがメモリを使い切ってしまったとしたら、おそらくそれは、Zend エンジンのハッカー あるいは PHP プログラマーのどちらかがまずいコードを書いたのでしょう。 したがって、メモリの使用量を制限するというのは、単にその言語の実行時に期待される内容に制約を加えるというものではありません。 単に、何かの手違いで開発環境が制御不能にならないようにしたり、実行時にバグが発生しないようにするための方法でしかありません。

ハッカーの視点で見たメモリ管理 API は、 libc (あるいはその他お好みのライブラリ) の malloc の実装にそっくりです。

メインメモリ API
プロトタイプ 説明
void *emalloc(size_t size) size バイトのメモリを確保します。
void *ecalloc(size_t nmemb, size_t size) size バイトのバッファを nmemb 要素だけ確保し、 ゼロで初期化します。
void *erealloc(void *ptr, size_t size) emalloc を使って確保したバッファ ptr のサイズを変更し、 size バイトにします。
void efree(void *ptr) ptr が指すバッファを解放します。このバッファは emalloc で確保したものでなければいけません。
void *safe_emalloc(size_t nmemb, size_t size, size_t offset) size バイトのバッファを nmemb ブロックぶんと、さらに offset バイトを確保します。 emalloc(nmemb * size + offset) と似ていますが、 オーバーフロー対策の特別な保護が追加されています。
char *estrdup(const char *s) NULL 終端文字列 s を保持できるだけのバッファを確保し、 s をそのバッファにコピーします。
char *estrndup(const char *s, unsigned int length) estrdup と似ていますが、NULL 終端文字列の長さが既知である場合に使います。

注意: エンジンのメモリ管理関数は、失敗しても NULL は返しません。 実行時にメモリの確保に失敗した場合は、エラーが発生します。

警告

コードをデプロイする前には常に valgrind を使いましょう。ハッカーとしてのたしなみです。 Zend エンジンが検出して報告してくれるのは、確保済みのメモリのリークだけです。 PHP 自体はサードパーティのソフトウェアに対する薄いラッパーに過ぎず、サードパーティのソフトウェアは Zend エンジンのメモリ管理は使いません。 さらに valgrind は、常に落ちるわけではないというエラーを捕捉してくれたり、 実行時に明確な影響が出るわけではないエラーを見つけてくれたりもします。 これらに対応することも大切で、回避できるリークは回避しておかなければいけません。

注意: 回避不能なリークもあります。ライブラリによってはプロセス終了のタイミングでメモリを解放する仕組みになっているものがあり (場合によっては普通にあり得ることです)、その場合は制御不能だからです。

--enable-debug を付けて構築したデバッグ環境では、次の例の leak 関数が実装された状態になり、 ユーザーのスクリプトからも呼べるようになります。

例1 リークの検出の実例

ZEND_FUNCTION(leak)
{
    long leakbytes = 3;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &leakbytes) == FAILURE) {
        return;
    }

    emalloc(leakbytes);
}

上の例の出力は、 たとえば以下のようになります。

[Thu Oct 22 02:14:57 2009]  Script:  '-'
/home/johannes/src/PHP_5_3/Zend/zend_builtin_functions.c(1377) :  Freeing 0x088888D4 (3 bytes), script=-
=== Total 1 memory leaks detected ===

注意: USE_ZEND_ALLOC=0 を指定するとメモリマネージャーの機能が停止し、 すべてのメモリ管理をシステムのデフォルトのアロケーターに任せます。これは、リークのデバッグ時に便利です。 それ以外の場面で使うべきではありません。