How can I convert the stringified version of array reference to actual array reference in Perl?

前端 未结 6 545
南笙
南笙 2020-11-29 11:11

Is there any way to get Perl to convert the stringified version e.g (ARRAY(0x8152c28)) of an array reference to the actual array reference?

For example



        
相关标签:
6条回答
  • 2020-11-29 11:49

    In case someone finds this useful, I'm extending tobyink's answer by adding support for detecting segmentation faults. There are two approaches I discovered. The first way locally replaces $SIG{SEGV} and $SIG{BUS} before dereferencing. The second way masks the child signal and checks if a forked child can dereference successfully. The first way is significantly faster than the second.

    Anyone is welcome to improve this answer.

    First Approach

    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;
    }
    

    I'm not sure if any other signals can occur in an attempt to access illegal memory.

    Second Approach

    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;
    }
    

    I'm not sure if the return value of waitpid needs to be checked.

    0 讨论(0)
  • 2020-11-29 11:50

    I'm not sure why you want to do this, but if you really need it, ignore the answers that use the tricks to look into memory. They'll only cause you problems.

    Why do you want to do this? There's probably a better design. Where are you getting that stringified reference from.

    Let's say you need to do it for whatever reason. First, create a registry of objects where the hash key is the stringified form, and the value is a weakened reference:

     use Scalar::Util qw(weaken);
    
     my $array = [ ... ];
    
     $registry{ $array } = $array;
    
     weaken( $registry{ $array } ); # doesn't count toward ref count
    

    Now, when you have the stringified form, you just look it up in the hash, checking to see that it's still a reference:

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

    You could also try Tie::RefHash and let it handle all of the details of this.

    There is a longer example of this in Intermediate Perl.

    0 讨论(0)
  • 2020-11-29 11:53

    Yes, you can do this (even without Inline C). An example:

    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";
    

    but don't do that. If your actual object is freed or reused, you may very well end up getting segfaults.

    Whatever you are actually trying to achieve, there is certainly a better way. A comment to another answer reveals that the stringification is due to using a reference as a hash key. As responded to there, the better way to do that is the well-battle-tested Tie::RefHash.

    0 讨论(0)
  • 2020-11-29 11:53

    The first question is: do you really want to do this?

    Where is that string coming from?

    If it's coming from outside your Perl program, the pointer value (the hex digits) are going to be meaningless, and there's no way to do it.

    If it's coming from inside your program, then there's no need to stringify it in the first place.

    0 讨论(0)
  • 2020-11-29 11:53

    The stringified version contains the memory address of the array object, so yes, you can recover it. This code works for me, anyway (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
    
    0 讨论(0)
  • 2020-11-29 11:59

    Yes, it's possible: use 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);
    
    0 讨论(0)
提交回复
热议问题