How do I get address of scalars?

D

Dez

I am trying to detect circular references inside a hash. I was thinking
of doing this by tracking the address of the variables. However, the
test program I have below is showing the same address for the scalars in
the hash, while the references in the hash are showing as expected.
Basically, I'm getting a false positive for hash member id and name.

I tried the code at http://www.perlmonks.org/?node_id=406446 but it also
is showing the same address, which I assume is the address of my $ref,
not the address of the variable passed in.

Any ideas?


---------- Output of Test Program -------

parent hash ref = 164306792 type1 = HASH blessed =
key = arr ref = 163412544 type1 = ARRAY blessed =
key = circ ref = 164306812 type1 = SCALAR blessed =
key = circ2 ref = 164306792 type1 = HASH blessed =
key = circ3 ref = 163412544 type1 = ARRAY blessed =
key = code ref = 164340068 type1 = CODE blessed =
key = has ref = 164340768 type1 = HASH blessed =
key = id ref = 164339168 type1 = notaref blessed =
key = name ref = 164339168 type1 = notaref blessed =
key = obj ref = 164306732 type1 = SCALAR blessed = JSON
key = regex ref = 164306712 type1 = REGEXP blessed = Regexp

---------- Test Program Below -----------

use strict;
use warnings;

use B;
use JSON;

my %tmap = qw(
B::NULL SCALAR
B::HV HASH
B::AV ARRAY
B::CV CODE
B::IO IO
B::GV GLOB
B::REGEXP REGEXP
);

sub refaddr($) {
ref($_[0]) ? 0+$_[0] : undef
}

sub chktype {
my $r = shift;

return unless length(ref($r));

my $t = ref(B::svref_2object($r));

return
exists $tmap{$t} ? $tmap{$t}
: length(ref($$r)) ? 'REF'
: 'SCALAR';
}

sub walk($) {
my $ref = shift;
my $type1 = chktype $ref;
my $type2 = ref $ref;

if (!defined $type1) {
$type1 = 'notaref';
}

my $blessed = ($type1 ne $type2) ? $type2 : '';

# if ref is a reference, get address to which it points
my $addr = $type2 ? refaddr $ref : refaddr \$ref;

return "ref = $addr\ttype1 = $type1\tblessed = $blessed\n";
}

my $test_hash = {
name => 'Some Name',
arr => ['a','b','3','5'],
has => {
key1 => 'val1',
key2 => 'val2',
key3 => 'val3'
},
regex => qr/thisisaregex/x,
code => sub {return 1},
id => 4,
obj => JSON->new
};

$test_hash->{circ} = \$test_hash->{name};
$test_hash->{circ2} = $test_hash;
$test_hash->{circ3} = $test_hash->{arr};

print "\n";
print "parent hash\t" . walk $test_hash;
foreach my $key (sort keys %{$test_hash}) {
print "key = $key\t" . walk $test_hash->{$key};
}
print "\n";
 
D

Dez

Quoth Dez said:
I am trying to detect circular references inside a hash. I was
thinking of doing this by tracking the address of the variables.
However, the test program I have below is showing the same address for
the scalars in the hash, while the references in the hash are showing
as expected. Basically, I'm getting a false positive for hash member id
and name.

Are you aware of Devel::Cycle? I think it does what you want, and it's
not quite as easy as you might think to do it correctly. However,
writing a function like this is a good way to learn about Perl's
reference model, so if you're doing it for that reason that's not a bad
idea.
I tried the code at http://www.perlmonks.org/?node_id=406446 but it
also is showing the same address, which I assume is the address of my
$ref, not the address of the variable passed in.

Any ideas?

---------- Output of Test Program -------

parent hash ref = 164306792 type1 = HASH blessed =
key = arr ref = 163412544 type1 = ARRAY blessed =
key = circ ref = 164306812 type1 = SCALAR blessed =
key = circ2 ref = 164306792 type1 = HASH blessed =
key = circ3 ref = 163412544 type1 = ARRAY blessed =
key = code ref = 164340068 type1 = CODE blessed =
key = has ref = 164340768 type1 = HASH blessed =
key = id ref = 164339168 type1 = notaref blessed =
key = name ref = 164339168 type1 = notaref blessed =
key = obj ref = 164306732 type1 = SCALAR blessed = JSON key =
regex ref = 164306712 type1 = REGEXP blessed = Regexp

---------- Test Program Below -----------

use strict;
use warnings;

use B;
use JSON;

my %tmap = qw(
B::NULL SCALAR B::HV HASH B::AV ARRAY B::CV CODE
B::IO IO B::GV GLOB B::REGEXP REGEXP
);

sub refaddr($) {
ref($_[0]) ? 0+$_[0] : undef
}

use Scalar::Util qw/refaddr/;
sub chktype {
my $r = shift;

return unless length(ref($r));

my $t = ref(B::svref_2object($r));

return
exists $tmap{$t} ? $tmap{$t}
: length(ref($$r)) ? 'REF'
: 'SCALAR';
}

use Scalar::Util qw/reftype/;
sub walk($) {

Don't use prototypes unless you have a very good reason. In this case
forcing scalar context on the argument is far more likely to be
confusing than helpful.

(About the only useful use for a ($) prototype is for functions
imitating ref(), so Scalar::Util's refaddr, reftype and blessed are
($)-prototyped.)
my $ref = shift;

You've just copied $_[0] into $ref. This means that when you check
refaddr \$ref below, this will not give you the address of the variable
you passed into the function, it will give you the address of the local
$ref variable. Under normal circumstances (if the function is not
reentrant and doesn't store refs to local variables somewhere external)
this will give you the same address every time.

What you want to do instead is take a reference to $_[0]: @_ is passed
as aliases, so this will give you a reference to the original variable
that was passed in.

my $ref = \$_[0];

You will obviously then need to replace $ref with $$ref throughout.
my $type1 = chktype $ref;
my $type2 = ref $ref;

if (!defined $type1) {
$type1 = 'notaref';
}

my $blessed = ($type1 ne $type2) ? $type2 : '';

use Scalar::Util qw/blessed/;

# if ref is a reference, get address to which it points my $addr =
$type2 ? refaddr $ref : refaddr \$ref;

I'm not sure if this is just a work-in-progress, but even when it's
working this will only detect circular refs of the form

$test_hash->{circ} = \$test_hash->{name};

Other forms, such as

$test_hash->{self} = \$test_hash->{self};

$test_hash->{ouro} = \$test_hash->{bouros};
$test_hash->{bouros} = \$test_hash->{ouro};

will require checking refaddr(\$_[0]) against a hash regardless of its
type, storing it in the hash if it wasn't there, and then recursing
through its referent if $_[0] is a ref itself.

Ben

Thanks for the reply. I'm doing this as a programming exercise. 8)
Basically, I'm trying to flatten a hash into an array of key/value pairs,
keeping track of parent / child relationships. I want to be able to
track references so that when I rebuild the object, I can restore
internal references (circular) if they exist. The reason for all this is
to attempt to write a dbd storage module. I know about Devel::Cycle, but
as my main goal is a dbd module, I don't want to depend on it. I'm also
doing all this to learn a little more about the internals of perl. I've
been following perl5porters for a while now, and the traffic is very
interesting.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top