灯火互联
管理员
管理员
  • 注册日期2011-07-27
  • 发帖数41778
  • QQ
  • 火币41290枚
  • 粉丝1086
  • 关注100
  • 终身成就奖
  • 最爱沙发
  • 忠实会员
  • 灌水天才奖
  • 贴图大师奖
  • 原创先锋奖
  • 特殊贡献奖
  • 宣传大使奖
  • 优秀斑竹奖
  • 社区明星
阅读:2862回复:0

PHP数组下标类型陷阱

楼主#
更多 发布于:2012-09-12 06:54


项目使用php语言开发,其中用到了MONGO DB存储;MONGO DB里的数据是强类型,php里的数据是弱类型,上周五我在MONGODB里查询一个数据总是找不到,最后发现问题是php数组的数值型字符串下标自动转变成了整数型下标;因此虽然php是弱类型语言,我们也要关注变量当前什么类型,熟悉php的类型自动转换规则,在一些类型敏感的地方要进行类型判断或者强制类型转换。

    以下示例程序简单解释了这个现象:



php代码
$id = "22";  
$arr1[$id] = "xxx";  
var_dump($arr1);  
$id = 22;  
$arr2[$id] = "xxx";  
var_dump($arr2);  
$id = "022";  
$arr3[$id] = "xxx";  
var_dump($arr3);  
$id = "2222222222222";  
$arr4[$id] = "xxx";  
var_dump($arr4);
$id = "22";$arr1[$id] = "xxx";var_dump($arr1);$id = 22;$arr2[$id] = "xxx";var_dump($arr2);$id = "022";$arr3[$id] = "xxx";var_dump($arr3);$id = "2222222222222";$arr4[$id] = "xxx";var_dump($arr4);
    这段程序的输出是:



php代码
array(1) {  
  [22]=>  
  string(3) "xxx"
}  
array(1) {  
  [22]=>  
  string(3) "xxx"
}  
array(1) {  
  ["022"]=>  
  string(3) "xxx"
}  
array(1) {  
  ["2222222222222"]=>  
  string(3) "xxx"
}
array(1) {  [22]=>  string(3) "xxx"}array(1) {  [22]=>  string(3) "xxx"}array(1) {  ["022"]=>  string(3) "xxx"}array(1) {  ["2222222222222"]=>  string(3) "xxx"}


    那么,php的数组字符串下标类型是怎么确定的呢?我们一起到php的源代码里看一看。

    首先,我们在Zend/zend_language_parser.y里搜索[,找到数组的语义解析规则:



php代码
object_dim_list:  
        object_dim_list '[' dim_offset ']'  { fetch_array_dim(;$$, ;$1, ;$3 TSRMLS_CC); }                          
    |   object_dim_list '{' expr '}'        { fetch_string_offset(;$$, ;$1, ;$3 TSRMLS_CC); }  
    |   variable_name { znode tmp_znode;  zend_do_pop_object(;tmp_znode TSRMLS_CC);  zend_do_fetch_property(;$$,  ;tmp_znode, ;$1 TSRMLS_CC);}    
;
object_dim_list:        object_dim_list '[' dim_offset ']'  { fetch_array_dim(;$$, ;$1, ;$3 TSRMLS_CC); }                             |   object_dim_list '{' expr '}'        { fetch_string_offset(;$$, ;$1, ;$3 TSRMLS_CC); }    |   variable_name { znode tmp_znode;  zend_do_pop_object(;tmp_znode TSRMLS_CC);  zend_do_fetch_property(;$$,  ;tmp_znode, ;$1 TSRMLS_CC);}   ;


   我们使用的是数组,因此使用第一个规则fetch_array_dim,在fetch_array_dim函数里,我们发现生成的opcode是ZEND_FETCH_DIM_W(84)。在Zend/zend_vm_def.h里,ZEND_FETCH_DIM_W的处理函数里zend_fetch_dimension_address处理取下标逻辑。



    继续跟踪下去,从zend_fetch_dimension_address函数到zend_fetch_dimension_address_inner,再到zend_symtable_update:



php代码
static inline int zend_symtable_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize,  void **pDest)                 \  
{  
    HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_update(ht, idx, pData, nDataSize, pDest));  
    return zend_hash_update(ht, arKey, nKeyLength, pData, nDataSize, pDest);                                        
}  
static inline int zend_symtable_update(HashTable *ht, char *arKey, uint nKeyLength, void *pData, uint nDataSize,  void **pDest)                 \{     HANDLE_NUMERIC(arKey, nKeyLength, zend_hash_index_update(ht, idx, pData, nDataSize, pDest));    return zend_hash_update(ht, arKey, nKeyLength, pData, nDataSize, pDest);                                      }


   HANDLE_NUMERIC这个宏很有意思,如果字符串下标arKey可转化为长整数idx,则调用zend_hash_index_update把数据插入到idx位置,否则调用zend_hash_update修改arKey位置的值 。我们看下宏的具体定义:



php代码
#define HANDLE_NUMERIC(key, length, func) {                                             \  
    register char *tmp=key;                                                             \  
                                                                                        \  
    if (*tmp=='-') {                                                                    \  
        tmp++;                                                                          \  
    }                                                                                   \  
    if ((*tmp>='0' ;; *tmp<='9')) do { /* possibly a numeric index */                   \  
        char *end=key+length-1;                                                         \  
        long idx;                                                                       \  
                                                                                        \  
        if (*tmp++=='0' ;; length>2) { /* don't accept numbers with leading zeros */    \  
            break;                                                                      \  
        }                                                                               \  
        while (tmp<end) {                                                               \  
            if (!(*tmp>='0' ;; *tmp<='9')) {                                            \  
                break;                                                                  \  
            }                                                                           \  
            tmp++;                                                                      \  
        }                                                                               \  
        if (tmp==end ;; *tmp=='\0') { /* a numeric index */                             \  
            if (*key=='-') {                                                            \  
                idx = strtol(key, NULL, 10);                                            \  
                if (idx!=LONG_MIN) {                                                    \  
                    return func;                                                        \  
                }                                                                       \  
            } else {                                                                    \  
                idx = strtol(key, NULL, 10);                                            \  
                if (idx!=LONG_MAX) {                                                    \  
                    return func;                                                        \  
                }                                                                       \  
            }                                                                           \  
        }                                                                               \  
    } while (0);                                                                        \  
}
#define HANDLE_NUMERIC(key, length, func) {                                             \    register char *tmp=key;                                                             \                                                                                        \    if (*tmp=='-') {                                                                    \        tmp++;                                                                          \    }                                                                                   \    if ((*tmp>='0' ;; *tmp<='9')) do { /* possibly a numeric index */                   \        char *end=key+length-1;                                                         \        long idx;                                                                       \                                                                                        \        if (*tmp++=='0' ;; length>2) { /* don't accept numbers with leading zeros */    \            break;                                                                      \        }                                                                               \        while (tmp<end) {                                                               \            if (!(*tmp>='0' ;; *tmp<='9')) {                                            \                break;                                                                  \            }                                                                           \            tmp++;                                                                      \        }                                                                               \        if (tmp==end ;; *tmp=='\0') { /* a numeric index */                             \            if (*key=='-') {                                                            \                idx = strtol(key, NULL, 10);                                            \                if (idx!=LONG_MIN) {                                                    \                    return func;                                                        \                }                                                                       \            } else {                                                                    \                idx = strtol(key, NULL, 10);                                            \                if (idx!=LONG_MAX) {                                                    \                    return func;                                                        \                }                                                                       \            }                                                                           \        }                                                                               \    } while (0);                                                                        \}
    从宏里我们知道了字符串下标自动转化为长整数下标的规则:

    1. 全部为数字,但是不能有前导0,比如arKey="0123"不会转化成123

    2. 不能超过long的表示范围(LONG_MIN, LONG_MAX),即(-2147483648, 2147483647)

喜欢0 评分0
游客

返回顶部