有没有办法让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
];
答案 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
的返回值。