如何将字符串化版本的数组引用转换为Perl中的实际数组引用?

时间:2009-11-04 01:53:48

标签: perl

有没有办法让Perl将数组引用的字符串化版本(例如ARRAY(0x8152c28))转换为实际的数组引用?

例如

perl -e 'use Data::Dumper; $a = [1,2,3];$b = $a; $a = $a.""; warn Dumper (Then some magic happens);'

会产生

$VAR1 = [
      1,
      2,
      3
    ];

6 个答案:

答案 0 :(得分:17)

是的,你可以这样做(即使没有内联C)。一个例子:

use strict;
use warnings;

# make a stringified reference
my $array_ref = [ qw/foo bar baz/ ];
my $stringified_ref = "$array_ref";

use B; # core module providing introspection facilities
# extract the hex address
my ($addr) = $stringified_ref =~ /.*(0x\w+)/;
# fake up a B object of the correct class for this type of reference
# and convert it back to a real reference
my $real_ref = bless(\(0+hex $addr), "B::AV")->object_2svref;

print join(",", @$real_ref), "\n";

但不要这样做。如果您的实际对象被释放或重用,您可能会很好 最终得到段错误。

无论你实际想要实现什么目标,肯定有更好的方法。 对另一个答案的评论表明,字符串化是由于使用引用作为哈希键。作为回应,更好的方法是经过良好的战斗测试 Tie::RefHash

答案 1 :(得分:6)

第一个问题是:你真的想这样做吗?

该字符串来自哪里?

如果它来自你的Perl程序之外,指针值(十六进制数字)将毫无意义,并且没有办法实现。

如果它来自您的程序内部,则无需首先对其进行字符串化。

答案 2 :(得分:5)

是的,可以:使用Devel::FindRef

use strict;
use warnings;
use Data::Dumper;
use Devel::FindRef;

sub ref_again {
   my $str = @_ ? shift : $_;
   my ($addr) = map hex, ($str =~ /\((.+?)\)/);
   Devel::FindRef::ptr2ref $addr;
}

my $ref = [1, 2, 3];
my $str = "$ref";
my $ref_again = ref_again($str);

print Dumper($ref_again);

答案 3 :(得分:4)

字符串化版本包含数组对象的内存地址,所以是的,您可以恢复它。无论如何,这段代码对我有用(Cygwin,perl 5.8):

use Inline C;
@a = (1,2,3,8,12,17);
$a = \@a . "";
print "Stringified array ref is $a\n";
($addr) = $a =~ /0x(\w+)/;
$addr = hex($addr);
$c = recover_arrayref($addr);
@c = @$c;
print join ":", @c;
__END__
__C__
AV* recover_arrayref(int av_address) { return (AV*) av_address; }

$ perl ref-to-av.pl
Stringified array ref is ARRAY(0x67ead8)
1:2:3:8:12:17

答案 4 :(得分:2)

我不确定你为什么要这样做,但如果你真的需要它,请忽略使用这些技巧来回顾内存的答案。它们只会给你带来麻烦。

你为什么要这样做?可能有更好的设计。你从哪里得到那个字符串化的参考文献。

假设你出于某种原因需要这样做。首先,创建一个对象注册表,其中哈希键是字符串化形式,值是弱化引用:

 use Scalar::Util qw(weaken);

 my $array = [ ... ];

 $registry{ $array } = $array;

 weaken( $registry{ $array } ); # doesn't count toward ref count

现在,当你有字符串形式时,你只需在哈希中查找它,检查它是否仍然是一个引用:

 if( ref $registry{$string} ) { ... }

你也可以尝试Tie::RefHash并让它处理这个的所有细节。

Intermediate Perl中有一个较长的例子。

答案 5 :(得分:0)

如果有人发现这个有用,我会通过增加对检测分段错误的支持来扩展tobyink的答案。我发现了两种方法。在解除引用之前,本地替换$SIG{SEGV}$SIG{BUS}的第一种方法。第二种方式masks the child signal并检查分叉子项是否可以成功解除引用。第一种方式明显快于第二种方式。

欢迎任何人改进这个答案。

第一种方法

sub unstringify_ref($) {
  use bigint qw(hex);
  use Devel::FindRef;

  my $str = @_ ? shift : $_;
  if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
    my $addr = (hex $1)->bstr;

    local $@;
    return eval {
      local $SIG{SEGV} = sub { die };
      local $SIG{BUS} = sub { die };
      return Devel::FindRef::ptr2ref $addr;
    };
  }
  return undef;
}

我不确定在尝试访问非法内存时是否会出现任何其他信号。

第二种方法

sub unstringify_ref($) {
  use bigint qw(hex);
  use Devel::FindRef;
  use Signal::Mask;

  my $str = @_ ? shift : $_;
  if (defined $str and $str =~ /\((0x[a-fA-F0-9]+)\)$/) {
    my $addr = (hex $1)->bstr;

    local $!;
    local $?;
    local $Signal::Mask{CHLD} = 1;
    if (defined(my $kid = fork)) {
      # Child -- This might seg fault on invalid address.
      exit(not Devel::FindRef::ptr2ref $addr) unless $kid;
      # Parent
      waitpid $kid, 0;
      return Devel::FindRef::ptr2ref $addr if $? == 0;
    } else {
      warn 'Unable to fork: $!';
    }
  }
  return undef;
}

我不确定是否需要检查waitpid的返回值。