Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Variable initialization / reinitialization

by Anonymous Monk
on Feb 06, 2009 at 20:53 UTC ( [id://742009]=perlquestion: print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Pretty basic problem but it's got me tearing my hear out. Short, sweet description: I'm calling a sub. At the beginning of the sub, I'm initializing a variable. The variable appears to be acting like a C static and retaining it's value between calls. Here's the code:
use strict; open TXT, "<masks.txt" or die "Can't open masks.txt\n"; while(<TXT>) { chomp; my @words = split(/ +/); print "Checking mask $words[2]\n"; my $maskvalid = ValidateMasks($words[2]); print "Mask is $maskvalid\n\n"; } sub ValidateMasks() { return 0 unless defined $_[0]; my @octets = split (/\./, $_[0]); print "\toctets array is @octets\n"; my $valid = 1; my $index = 0; my $flagvalue = 0; while($index < 4) { $valid = ValidateOctet($octets[$index]); $index++; } return $valid; sub ValidateOctet() { return 0 unless defined $_[0]; # bad if no argument passed return 0 unless $valid; # return bad if any previous argument + is bad my $oc = $_[0]; return 0 if $oc > 255; # bad if value is above 255 print "\tchecking octet $oc\n"; return 1 if ($oc eq $flagvalue); # good if equal to flag valu +e print "\t\tUnequal: oc is $oc, flagvalue is $flagvalue\n"; return 0 if $flagvalue; # bad if this is a second non-zero an +d it's not equal to 255. $flagvalue = 255; # All further octets after this one must be + 255 # This is the first non-zero octet. # Should be equal to a power of 2 minus 1 # X is a power of 2 if (X & X-1) = 0 # $oc + 0 forces value to number vice string my $bitand = ($oc + 0) & ($oc + 1); return ( $bitand ? 0 : 1); } }
And the data file:
field1 10.1.253.11 0.0.0.0 field1 10.1.254.0 0.0.0.64 field1 10.1.254.128 0.0.0.63 field1 10.1.158.0 15.255.0.255 field1 10.1.160.0 0.0.0.37 field1 10.1.161.0 0.0.146.255
And the (truncated) output:
Checking mask 0.0.0.0 octets array is 0 0 0 0 checking octet 0 checking octet 0 checking octet 0 checking octet 0 Mask is 1 Checking mask 0.0.0.64 octets array is 0 0 0 64 checking octet 0 checking octet 0 checking octet 0 checking octet 64 Unequal: oc is 64, flagvalue is 0 Mask is 0 Checking mask 0.0.0.63 octets array is 0 0 0 63 checking octet 0 Unequal: oc is 0, flagvalue is 255 checking octet 0 Unequal: oc is 0, flagvalue is 255 checking octet 0 Unequal: oc is 0, flagvalue is 255 checking octet 63 Unequal: oc is 63, flagvalue is 255 Mask is 0
Right here: Checking mask 0.0.0.63
octets array is 0 0 0 63
checking octet 0
Unequal: oc is 0, flagvalue is 255

$flagvalue is 255. It should have been initialized to 0 when ValidateMasks() was called, shouldn't it? It appears to be retaining the value from the previous time it was called. Am I overlooking something obvious or do I really not understand Perl initialization and scope?

Replies are listed 'Best First'.
Re: Variable initialization / reinitialization
by Bloodnok (Vicar) on Feb 06, 2009 at 21:04 UTC
    Had you enabled full srictures i.e. use warnings; use strict;, you would have seen the following...
    Variable "$valid" will not stay shared at test.pl line 29. Variable "$flagvalue" will not stay shared at test.pl line 34. main::ValidateOctet() called too early to check prototype at test.pl l +ine 22. main::ValidateMasks() called too early to check prototype at test.pl l +ine 9.
    All of which stem from the Pascal-like declaration of a sub within a sub.

    A user level that continues to overstate my experience :-))
      Thanks for the response. I've never used Pascal, so I can't comment there. The purpose of the nested subs was to avoid having to either declare multiple global (that is, file level) variables or pass multiple arguments and accept multiple returns via an array pointer or some such. I still don't understand why $flagvalue is retaining it's value when control clearly exits and reenters the parent sub (and the warnings don't make much sense either) but I'm rewriting the flow slightly and removing the nested subs (via the repugnant file level variables) and it seems to be working...

        I'd get rid of the nested sub altogether and take advantage of Perl's array handling to clean up the loop handling. Using early exits as soon as an error is detected cleans up the code and reduces execution time (although that's unlikely to be an issue). Consider:

        use strict; use warnings; open TXT, "<masks.txt" or die "Can't open masks.txt\n"; while (<TXT>) { chomp; my @words = split (/ +/); print "Checking mask $words[2]\n"; my $maskvalid = ValidateMasks ($words[2]); print "Mask is $maskvalid\n\n"; } sub ValidateMasks { my $mask = shift; return 0 unless defined $mask; my @octets = split /\./, $mask; print "\toctets array is @octets\n"; return 0 unless 4 == @octets; # Bad if too few parts my $flagvalue = 0; for my $octet (@octets) { return 0 if $octet > 255; # bad if value is above 255 print "\tchecking octet $octet\n"; next if ($octet eq $flagvalue); # good if equal to flag val +ue print "\t\tUnequal: oc is $octet, flagvalue is $flagvalue\n"; # bad if this is a second non-zero and it's not equal to 255. return 0 if $flagvalue; $flagvalue = 255; # All further octets after this one must +be 255 # This is the first non-zero octet. # Should be equal to a power of 2 minus 1 # X is a power of 2 if (X & X-1) = 0 # $octet + 0 forces value to number vice string return 0 unless ($octet + 0) & ($octet + 1); } return 1; }

        Oh, and don't use prototyped subs, they generally don't do what you expect (see Gratuitous use of Perl Prototypes).


        Perl's payment curve coincides with its learning curve.
        I still don't understand why $flagvalue is retaining it's value when control clearly exits and reenters the parent sub....

        Lexical variables don't follow the rules of dynamic scoping. They follow the rules of lexical scoping.

        NP

        If you insist on using nested subs, there are ways & means to implement them e.g. using anonymous subs e.g. (IIRC - since I've not used them since I discovered the wonders of perl)...

        sub some_sub { my $var; my $nested_sub = sub { . . if ($var eq q/value/) { # Do something } else { # Do something else $var = q/some_other_val/; } }; # Do some stuff $var = q/value/; &$nested_sub(args); if ($val eq q/some_other_val/) { # Do some more stuff } else { # Do something else entirely } . }

        A user level that continues to overstate my experience :-))

        The thing is that you cannot have locally scoped subroutines. All named subroutines are implicitly package level things.

        Now, you can declare a named subroutine inside another subroutine. That inner subroutine comes into existence without having to execute the outer subroutine. But, because all named subroutines are all at package level, the inner subroutine can be called from outside the outer one.

        So, if the inner subroutine refers to lexical variables belonging to the outer subroutine, Perl has a problem. Those variables notionally don't exist until the outer subroutine is entered. It's a mess, which Perl resolves by inventing new versions of the variables apparently shared by the inner and outer subroutines. Those new versions are effectively outside the inner subroutine -- so behave, as you observed, much like 'C' statics.

        So:

        sub outer { my $foo ; .... sub inner { $foo++ ; } ; } ;
        is effectively the same as:
        sub outer { my $foo ; .... } ; { my $foo ; sub inner { $foo++ ; } ; } ;

        (You can declare named subroutines in other blocks, such as inside an eval, which is probably why you can do so in a subroutine. But that's pure speculation.)

        There's another discussion which also covers nested named subroutines...

Re: Variable initialization / reinitialization
by hbm (Hermit) on Feb 07, 2009 at 01:45 UTC

    This reminds me a bit of what finally pushed me over the edge from bash to Perl many years ago - validating and sorting lists of IP addresses. In hindsight, I bet there's a module for the task, but it was a good exercise. Anyhow, borrowing from that years-old effort:

    use strict; use warnings; while(<DATA>){ chomp; my $mask = (split(/\s+/, $_))[2]; print "Mask $mask is ", &Valid($mask), "\n"; } sub Valid { my @octs; local $_ = shift; return 0 if !defined $_; return 0 if m{(?:(?:^|\.)[^0]\d*\.)(?!255)}; return 0 if (@octs = split(/\./, $_)) ne 4; return 0 if grep {!m/^(?:0|3|7|15|31|63|127|255)$/} @octs; return 1; } __DATA__ field1 10.1.253.11 0.0.0.0 field1 10.1.254.0 0.0.0.64 field1 10.1.254.128 0.0.0.63 field1 10.1.158.0 15.255.0.255 field1 10.1.160.0 0.0.0.37 field1 10.1.161.0 0.0.146.255 field1 10.1.161.0 0.0.255 field1 10.1.161.0 0.0.255.255 field1 10.1.161.0 255.0.0.0 field1 10.1.158.0 15.255.255.127

    I'm not sure I followed your non-zero/non-255 rule. I interpreted it as, once there's a non-zero octet, all thereafter must be 255.

      Just wanted to thank you for your comment. It made a light go on in my head. The code is intended to validate an inverse mask. That's the inverse of a subnet mask - it masks off the network portion and keeps the host portion of an IP. In binary, that means it's 0 or more zeros followed by the rest of the string being all 1s. So, in dotted decimal format, you'll get 0 or more 0 octets, followed by 1 non-zero octet that is 1 less than a power of 2, followed by octets that are 255. But the binary representation is actually much easier to check for! So, with some help from The Perl Cookbook (for the pack/unpack cycle to convert a number to its binary string):
      sub ValidateMasks() { my $mask = shift; return 0 if !defined $mask; my $binary_rep; my @octets = split (/\./, $mask); for my $octet (@octets) { $binary_rep .= substr(unpack("B32", pack("N", $octet)), -8); } return $binary_rep =~ /^0*1*$/; }

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://742009]
Approved by Bloodnok
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (1)
As of 2024-04-15 16:17 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found