てきとうなさいと べぇたばん

PHPで配列の初期化のオペコードを見たあと、PHPのソースを読んで思ったこと

オペコード一覧を見たい

PHPで、オペコードの一覧を見たいなぁと思った。

vldのインストール

PECLにvldというものがあるので、インストール。

$ sudo aptitude install php-pear
$ sudo pecl install channel://pecl.php.net/vld-0.12.0
downloading vld-0.12.0.tgz ...
Starting to download vld-0.12.0.tgz (16,587 bytes)
......done: 16,587 bytes
11 source files, building
running: phpize
sh: 1: phpize: not found
If the command failed with 'phpize: not found' then you need to install php5-dev packageYou can do it by running 'apt-get install php5-dev' as a root userERROR: `phpize' failed

phpizeがないと言われたので、php5-devパッケージをインストール

$ sudo aptitude install php5-dev
$ sudo pecl install channel://pecl.php.net/vld-0.12.0

インストール後として、php.iniに有効にさせる。ubuntuなので、/etc/php5/cli/conf.d/30-vld.iniというファイルをつくって有効にした。

extension=vld.so

前回の配列の初期化

前回のオペコードを表示させてみた。arrayの初期化を見たくなったので。

$ php -dvld.active=1 -dvld.execute=0 -f test.php
Finding entry points
Branch analysis from position: 0
Return found
filename:       /home/tekitoh/php/test.php
function name:  (null)
number of ops:  29
compiled vars:  !0 = $b, !1 = $c
line     # *  op                           fetch          ext  return  operands
---------------------------------------------------------------------------------
   3     0  >   INIT_ARRAY                                       ~0      0, -1
         1      ASSIGN                                                   !0, ~0
   4     2      SEND_REF                                                 !0
         3      DO_FCALL                                      1  $2      'array_pop'
         4      ASSIGN                                                   !1, $2
   5     5      IS_EQUAL                                         ~4      !1, 0
         6      SEND_VAL                                                 ~4
         7      DO_FCALL                                      1          'assert'
   7     8      SEND_REF                                                 !0
         9      SEND_VAL                                                 0
        10      DO_FCALL                                      2  $6      'array_push'
        11      SEND_VAR_NO_REF                               6          $6
        12      SEND_VAL                                                 1
        13      DO_FCALL                                      2          'assert'
   8    14      SEND_REF                                                 !0
        15      SEND_VAL                                                 0
        16      DO_FCALL                                      2  $8      'array_push'
        17      SEND_VAR_NO_REF                               6          $8
        18      SEND_VAL                                                 2
        19      DO_FCALL                                      2          'assert'
   9    20      SEND_VAR                                                 !0
        21      DO_FCALL                                      1          'var_dump'
  11    22      SEND_REF                                                 !0
        23      SEND_VAL                                                 0
        24      DO_FCALL                                      2  $11     'array_push'
        25      SEND_VAR_NO_REF                               6          $11
        26      SEND_VAL                                                 3
        27      DO_FCALL                                      2          'assert'
  13    28    > RETURN                                                   1

branch: #  0; line:     3-   13; sop:     0; eop:    28
path #1: 0,

3行目、0番目のINIT_ARRAYのところを見てみると、INIT_ARRAYでは、zend_vm_execute.hの3848行目にある以下のメソッドが実行される。Zend/zend_execute.hにてexecute_ex関数のOPLINE->handlerメソッドの内容の模様。

static int ZEND_FASTCALL  ZEND_INIT_ARRAY_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

更に、以下のメソッドを実行する。zend_vm_execute.hの3762行目。

static int ZEND_FASTCALL  ZEND_ADD_ARRAY_ELEMENT_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

負の値のインデックスについて

3800行目で、offsetへの値をopline->op2.zvと代入している。そのoffsetから、zval.value.lvalの値をローカル変数hvalに代入する。これがインデックスになる。しかし、zval.value.lvalはlongであり、hvalはulong(unsigned long)である。3811行目で、zend_hash_index_updateが実行される。その際に、hvalは、zval.value.lvalが-1になっているので18446744073709551615(64bit)になってしまう。

(gdb) print hval
$19 = 18446744073709551615
(gdb) print offset
$20 = (zval *) 0x7ffff7fe4f40
(gdb) print offset->value.lval
$21 = -1

とはいえ、ここはソースの3行目のarray(-1 => 0)の部分で、インデックスは-1であるのだから、ここが桁あふれしてるのはおかしい…あれ。

Bucket構造体を見てみる。hが数値のインデックスになる部分だけど、ulongである。

typedef struct bucket {
    ulong h;                        /* Used for numeric indexing */
    uint nKeyLength;
    void *pData;
    void *pDataPtr;
    struct bucket *pListNext;
    struct bucket *pListLast;
    struct bucket *pNext;
    struct bucket *pLast;
    const char *arKey;
} Bucket;

PHPのarrayで負の数のインデックスってどうやって取り回すのだという疑問がつく。色々考えてみたのだけど、もうここらへんを調べきるのが手間になってきたのでここからは推測もはいるけれども。

unsigned longをlongでキャストしてしまえば、負の数取りまわせるんじゃね?ということだろうか?

んじゃあ、どうしてunsigned long使ってるんだろう?という純粋な疑問は残るんだ。

歯になにか詰まったような感じがあるけどここでひとまずおしまい。すんません。

さんこう