nDiki

2011年5月25日 (水)

なぜ Perl で配列に対して defined を使ってはいけないのか?

defined の挙動で相談されたのでソースコードを見てみたら、配列に対して defined を呼んでいた。 最近の Perl では配列に対して defined を使うのは非推奨である(perldata や perlfunc 参照)。 ほとんどの人が望むような判定結果は返ってこない*1

perl -e '@a = (); print defined @a ? 1 : 0; push @a, 1; print defined @a ? 1 : 0; shift @a; print defined @a ? 1 : 0'

配列が空かどうかならスカラーコンテキストで評価するだけで OK なのだが、Perl プログラミング経験上、1度は defined を使用してしまうだろう。 ただ通常は警告が出るのですぐ気がつく。 この警告は Perl 5.6.0 以降で出る。

 #!/usr/bin/perl

 use warnings;
 use strict;

 my @a = ();
 print defined @a ? "defined\n" : "undefined\n";
 push @a, 1;
 print defined @a ? "defined\n" : "undefined\n";
 shift @a;
 print defined @a ? "defined\n" : "undefined\n";

 # defined(@array) is deprecated at test.pl line 7.
 #         (Maybe you should just omit the defined()?)
 # defined(@array) is deprecated at test.pl line 9.
 #         (Maybe you should just omit the defined()?)
 # defined(@array) is deprecated at test.pl line 11.
 #          (Maybe you should just omit the defined()?)
 # undefined
 # defined
 # defined

しかしながら、配列への参照をデリファレンスしたものに defined を呼んでも警告を出してくれない。 相談ではまっていたのはこのケースだった。

 #!/usr/bin/perl

 use warnings;
 use strict;

 my $a = [];
 print defined @$a ? "defined\n" : "undefined\n";
 push @$a, 1;
 print defined @$a ? "defined\n" : "undefined\n";
 shift @$a;
 print defined @$a ? "defined\n" : "undefined\n";

 # undefined
 # defined
 # defined

なお配列(とハッシュ)に対する defined はメモリが割り当てられたかどうかを得るために使われていた。 Devel::Peek::Dump の結果を見てみると次のような感じ。 配列に要素を push した後に pop して空にしても、メモリは割り当てられた状態になるので defined が真を返すようになるのである。

 #!/usr/bin/perl

 use warnings;
 use strict;
 use Devel::Peek 'Dump';

 my @a = ();
 Dump(\@a);
 push @a, 1;
 Dump(\@a);
 shift @a;
 Dump(\@a);

 # SV = IV(0x9a6d064) at 0x9a6d068
 #   REFCNT = 1
 #   FLAGS = (TEMP,ROK)
 #   RV = 0x9a7dcd8
 #   SV = PVAV(0x9a6e0a8) at 0x9a7dcd8
 #     REFCNT = 2
 #     FLAGS = (PADMY)
 #     ARRAY = 0x0
 #     FILL = -1
 #     MAX = -1
 #     ARYLEN = 0x0
 #     FLAGS = (REAL)
 # SV = IV(0x9a6d184) at 0x9a6d188
 #   REFCNT = 1
 #   FLAGS = (TEMP,ROK)
 #   RV = 0x9a7dcd8
 #   SV = PVAV(0x9a6e0a8) at 0x9a7dcd8
 #     REFCNT = 2
 #     FLAGS = (PADMY)
 #     ARRAY = 0x9a78a20
 #     FILL = 0
 #     MAX = 3
 #     ARYLEN = 0x0
 #     FLAGS = (REAL)
 #     Elt No. 0
 #     SV = IV(0x9a6d064) at 0x9a6d068
 #       REFCNT = 1
 #       FLAGS = (IOK,pIOK)
 #       IV = 1
 # SV = IV(0x9a6d064) at 0x9a6d068
 #   REFCNT = 1
 #   FLAGS = (TEMP,ROK)
 #   RV = 0x9a7dcd8
 #   SV = PVAV(0x9a6e0a8) at 0x9a7dcd8
 #     REFCNT = 2
 #     FLAGS = (PADMY)
 #     ARRAY = 0x9a78a24 (offset=1)
 #     ALLOC = 0x9a78a20
 #     FILL = -1
 #     MAX = 2
 #     ARYLEN = 0x0
 #     FLAGS = (REAL)

ちなみに Perl 5.14.0 の pp_hot.c を見ると以下のようになっている。 配列だと AvMAX が 0 以上になっていれば真になる(十分条件)。 上の例でも pop した後も MAX = 2 となっていることから、defined が真を返しているわけだ。

 PP(pp_defined)
 {
     dVAR; dSP;
     register SV* sv;
     bool defined;
     const int op_type = PL_op->op_type;
     const bool is_dor = (op_type == OP_DOR || op_type == OP_DORASSIGN);

     if (is_dor) {
         PERL_ASYNC_CHECK();
         sv = TOPs;
         if (!sv || !SvANY(sv)) {
             if (op_type == OP_DOR)
                 --SP;
             RETURNOP(cLOGOP->op_other);
         }
     }
     else {
         /* OP_DEFINED */
         sv = POPs;
         if (!sv || !SvANY(sv))
             RETPUSHNO;
     }

     defined = FALSE;
     switch (SvTYPE(sv)) {
     case SVt_PVAV:
         if (AvMAX(sv) >= 0 || SvGMAGICAL(sv) || (SvRMAGICAL(sv) && mg_find(sv, PERL_MAGIC_tied)))
             defined = TRUE;
         break;
     case SVt_PVHV:
         if (HvARRAY(sv) || SvGMAGICAL(sv) || (SvRMAGICAL(sv) && mg_find(sv, PERL_MAGIC_tied)))
             defined = TRUE;
         break;
     case SVt_PVCV:
         if (CvROOT(sv) || CvXSUB(sv))
             defined = TRUE;
         break;
     default:
         SvGETMAGIC(sv);
         if (SvOK(sv))
             defined = TRUE;
         break;
     }

     if (is_dor) {
         if(defined)
             RETURN;
         if(op_type == OP_DOR)
             --SP;
         RETURNOP(cLOGOP->op_other);
     }
     /* assuming OP_DEFINED */
     if(defined)
         RETPUSHYES;
     RETPUSHNO;
 }

結論としては、良い子のみなさんは配列やハッシュに defined を使わないでねということで。

*1以下 Perl 5.14.0 で確認。

スポンサード リンク
[ 5月25日全て ]

About Me

Naney Naney (なにい)です。株式会社ミクシィで SNS 事業の部長をしています。

nDiki1999年1月に始めたコンピュータ日誌を前身とする NaneyWeb 日記(兼パーソナルナレッジベース)です。ちょっとしたノートは nNote にあります。

※内容は個人的見解であり所属組織とは関係ありません。

follow us in feedly

月別インデックス
Process Time: 0.1445s / load averages: 0.44, 0.42, 0.35
nDiki by WATANABE Yoshimasa (Naney, Google profile)
Powered by DiKicker