生成される hash 値が php7 で少しだけ変わった件

この記事は 闇PHP Advent Calendar 2015 10日目 です

php連想配列で用いられているハッシュ関数は DJBX33A と呼ばれるものです。


実装は php5.6 までは Zend/zend_hash.h 、php7 からは Zend/zend_string.h に、どちらも zend_inline_hash_func という関数で実装されていますが、実装はわずかに異なります。

以下は、php-5.6.16 と php-7.0.0 の zend_inline_hash_func を抜粋したものの diff です。

一番多い修正は、引数名が、arKey から str、 nKeyLength から len に変わったことです。
これはおそらく key のハッシュ値生成目的だったものが、zend_string のハッシュ生成になったので、それに合わせてでしょう。


5381 という初期値が Z_UL(5381) になりました (20行目)
Z_UL は環境に応じて、値の後に U、UL、ULL を付与するマクロのようです。おそらくコンパイラの最適化のためでしょうが、具体的な理由はよく分かりません。


上記は、計算されるハッシュ値には影響しない変更です。
しかし、関数の最後にハッシュ値に変更が加わる修正が入りました。ハッシュ値の計算後、値が 0 だったら 最上位ビットを立てる という処理です。

コメントには、"ハッシュ値はゼロになってはいけない" とありますが、なぜゼロではいけないのかの理由は示されていません。

git blame して確認して見ると、 https://github.com/php/php-src/commit/bdf7fc67d8a146b4a5387c56268181c63047dfe6 のコミットで追加されており、そのコミットログに理由が書かれていました。

zend_string は読み取り専用メモリ領域に置かれることがあり、ハッシュ値が 0 になってしまうと再計算が行われるためにメモリを書き換えようとしてクラッシュするようです。


読み取り専用メモリ領域に置かれる状況や、ハッシュ値の再計算が行われるコードの箇所は調べきれませんでしたが、とりあえず、そういうことのようです。