PHP Advent Calendar jp 2011 #16 です。
前の記事は @bimihoujyun の PHP5.4の新機能、「trait」を知る!(PHP Advent Calendar jp 2011Day15) です。
明日から使える Tips が並ぶ PHP Advent Calendar 2011。
ここらでちょっと趣向を変えて、 PHP インタプリタでも読んでみませんか?
今回の概要
PHP の文字列比較について、緩やかな比較演算子(==, <, >)による比較を避けたほうが良いとされます。1
これは PHP が文字列を数値として比較する場合があるためです。
例えば、以下のコードは SAME と表示されます。
<?php
if ( "01" == "1" ) {
echo "SAME";
} else {
echo "DIFFERENT";
}
このことについて PHP のインタプリタをざっくりと読んで、理解を深めてみましょう。
PHP のバージョンは 5.3.8 とします。
緩やかな比較演算子を実装している箇所
文字列同士を == 演算子を使用して比較した場合、
インタプリタ内では zendi_smart_strcmp 関数が呼び出されます。
( is_equal_function 関数 -> compare_function 関数 -> zendi_smart_strcmp 関数の順に呼び出されます )
zendi_smart_strcmp 関数は引数 result の値を書き換えます。
result が負の数なら s1 < s2, 0 なら s1 = s2, 正の数なら s1 > s2 と判断します。
この関数は php-5.3.8/Zend/zend_operators.c にて実装されています。
zendi_smart_strcmp 関数を眺める
では、ソースコードを眺めてみましょう。
一番外側の if 文に注目してください。
数字と判断できるなら数値として比較しています。2
そうでなければ、文字列として比較しています。
ZEND_API void zendi_smart_strcmp(zval *result, zval *s1, zval *s2) /* {{{ */
{
int ret1, ret2;
long lval1, lval2;
double dval1, dval2;
if ((ret1=is_numeric_string(Z_STRVAL_P(s1), Z_STRLEN_P(s1), &lval1, &dval1, 0)) &&
(ret2=is_numeric_string(Z_STRVAL_P(s2), Z_STRLEN_P(s2), &lval2, &dval2, 0))) {
if ((ret1==IS_DOUBLE) || (ret2==IS_DOUBLE)) {
if (ret1!=IS_DOUBLE) {
dval1 = (double) lval1;
} else if (ret2!=IS_DOUBLE) {
dval2 = (double) lval2;
} else if (dval1 == dval2 && !zend_finite(dval1)) {
/* Both values overflowed and have the same sign,
* so a numeric comparison would be inaccurate */
goto string_cmp;
}
Z_DVAL_P(result) = dval1 - dval2;
ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_DVAL_P(result)));
} else { /* they both have to be long's */
ZVAL_LONG(result, lval1 > lval2 ? 1 : (lval1 < lval2 ? -1 : 0));
}
} else {
string_cmp:
Z_LVAL_P(result) = zend_binary_zval_strcmp(s1, s2);
ZVAL_LONG(result, ZEND_NORMALIZE_BOOL(Z_LVAL_P(result)));
}
}
/* }}} */
確かに PHP で緩やかな比較演算子を用いて文字列を比較するのは問題がありそうです。
どっちも文字列と判断されたらどうなるの?
zend_binary_zval_strcmp 関数を呼び出します。
zend_binary_zval_strcmp 関数は zend_binary_strcmp 関数を呼び出します。
zend_binary_strcmp 関数はメモリの状態を比較します。3
ZEND_API int zend_binary_strcmp(const char *s1, uint len1, const char *s2, uint len2) /* {{{ */
{
int retval;
retval = memcmp(s1, s2, MIN(len1, len2));
if (!retval) {
return (len1 - len2);
} else {
return retval;
}
}
/* }}} */
文字列同士を <や > で比較できるのは、この実装によるものです。4
まとめ
PHP では文字列の比較時に緩やかな比較演算子を用いるべきではないと言われています。
本記事では PHP のインタプリタを追って、その問題点を確認しました。
PHP で文字列比較をする際は、緩やかな比較演算子を使用しないように心がけましょう。
なお、厳密な比較演算子 === を使用するのは問題ありません。
気になる人は is_identical_function 関数を読んでみてください。
次の人紹介
次は @omoon です。
関西勢が続きますね。 :D
よろしくお願い致します。
- 1: PHPの文字列比較で気をつけるべきこと – 暗黙の型変換 – EC studio 技術ブログ
- 2: is_numeric_string 関数は zend_operators.h にてインライン関数として定義されています
- 3: C の標準ライブラリ関数 memcmp を使用します
- 4: memcmp で文字コードの大小を比較している
- Newer: 2011 年の勉強をざっと振り返る
- Older: LTSpiral03 を開催しました #LTSpl
Comments:0
Trackbacks:0
- Trackback URL for this entry
- http://my-rest.icca.jp/blog/2011/12/769/trackback/
- Listed below are links to weblogs that reference
- [PHP] == による文字列比較をしてはいけないことを理解する #phpadvent2011 16th from Prog Blog From 憩い場所