<!-- category: hidden-gems -->
Devel::Peek - X-Ray Vision for Perl Variables
The number 42 is a number. Obviously. But is it a number inside Perl? Or is it a string that looks like a number? Or is it both at the same time?use Devel::Peek; my $x = 42; Dump($x);
That is the internal representation. An integer scalar (IV) with the value 42. The flags say IOK: "integer value is OK to use." Perl knows this is a number and nothing else.SV = IV(0x7fa3b2c04e90) at 0x7fa3b2c04ea0 REFCNT = 1 FLAGS = (IOK,pIOK) IV = 42
Now do something with it as a string:
my $x = 42; my $s = "$x"; # force string context Dump($x);
Now it has BOTH an integer value (IV = 42) AND a string value (PV = "42"). Two representations cached in the same variable. Perl computed the string version when you interpolated, then kept it around in case you need it again.SV = PVIV(0x7fa3b2c14280) at 0x7fa3b2c04ea0 REFCNT = 1 FLAGS = (IOK,POK,pIOK,pPOK) IV = 42 PV = 0x7fa3b2d05230 "42"\0 CUR = 2 LEN = 10
This is Devel::Peek. It shows you what Perl actually stores
inside a variable, at the C structure level. When something
behaves unexpectedly, this is where you go to find out why.
Part 1: THE DUMP FUNCTION
Dump is the main function. Give it a variable, it shows you
everything:
use Devel::Peek; my $num = 3.14; Dump($num);
NV means "numeric value" (floating point). The flags say NOK: "numeric value is OK."SV = NV(0x7fa3b2c04e88) at 0x7fa3b2c04ea0 REFCNT = 1 FLAGS = (NOK,pNOK) NV = 3.14
A string:
my $str = "hello"; Dump($str);
PV means "pointer value" (string). POK means "pointer value is OK." CUR is the current length. LEN is the allocated buffer size.SV = PV(0x7fa3b2c04e78) at 0x7fa3b2c04ea0 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x7fa3b2d05230 "hello"\0 CUR = 5 LEN = 10
The \0 at the end of the PV is the null terminator. Perl keeps
it there for C compatibility, but it is not part of the string
length.
Part 2: READING THE FLAGS
The FLAGS line is the most important part of the dump. It tells you exactly what Perl thinks this variable is:The "p" versions (pIOK, pNOK, pPOK) are internal flags. For debugging purposes, the non-p versions are what you care about.FLAG MEANING ------ ------------------------------------------ IOK Integer value is valid NOK Numeric (float) value is valid POK String (PV) value is valid pIOK Private: integer value is valid pNOK Private: numeric value is valid pPOK Private: string value is valid ROK Reference value is valid UTF8 String is stored as UTF-8
When you see multiple flags like (IOK,POK,pIOK,pPOK), it means
the variable has both an integer and a string representation
cached. Perl will use whichever one the current operation needs
without converting.
Part 3: THE DUAL-NATURED SCALAR
This is the big reveal. A Perl scalar can be a string AND a number simultaneously:use Devel::Peek; my $x = "42"; Dump($x); say "--- after numeric use ---"; my $y = $x + 0; Dump($x);
After the first dump,SV = PV(0x...) at 0x... REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x... "42"\0 CUR = 2 LEN = 10 --- after numeric use --- SV = PVIV(0x...) at 0x... REFCNT = 1 FLAGS = (IOK,POK,pIOK,pPOK) IV = 42 PV = 0x... "42"\0 CUR = 2 LEN = 10
$x is purely a string (POK only). After
we use it as a number ($x + 0), Perl converts it to an integer
and caches the result. Now it has both IV = 42 and PV = "42".
The next time you use $x as a number, Perl grabs the cached IV
directly. No string-to-number conversion needed.
This caching is why Perl is faster than you might expect at repeated numeric operations on "number-like" strings.
Part 4: WHY == AND EQ GIVE DIFFERENT RESULTS
Here is a classic Perl confusion thatDevel::Peek explains
instantly:
What is going on? Look at the internals:my $a = "00"; my $b = "0"; say $a == $b ? "equal" : "not equal"; # equal (numeric) say $a eq $b ? "equal" : "not equal"; # not equal (string)
use Devel::Peek; my $a = "00"; Dump($a);
The string "00" is stored as two characters. WhenSV = PV(0x...) at 0x... REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x... "00"\0 CUR = 2 LEN = 10
== compares,
it converts both to numbers: "00" becomes 0, "0" becomes 0, they
are equal. When eq compares, it compares the raw strings:
"00" vs "0", different lengths, not equal.
Neither answer is wrong. You asked two different questions. The
== asked "are these the same number?" The eq asked "are these
the same string?" Devel::Peek shows you why: the PV is "00",
which is a different string from "0" but the same number as 0.
Part 5: SPOTTING THE UTF-8 FLAG
The UTF8 flag is critical for debugging encoding issues:use Devel::Peek; my $bytes = "\xc3\xa9"; # raw bytes my $char = "\x{e9}"; # Unicode character Dump($bytes); say "---"; Dump($char);
Both have the same bytes in the PV. But the second one has the UTF8 flag set, and the dump shows the decoded character in brackets:SV = PV(0x...) at 0x... REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x... "\303\251"\0 CUR = 2 LEN = 10 --- SV = PV(0x...) at 0x... REFCNT = 1 FLAGS = (POK,pPOK,UTF8) PV = 0x... "\303\251"\0 [UTF8 "\x{e9}"] CUR = 2 LEN = 10
[UTF8 "\x{e9}"].
With the UTF8 flag, length($char) returns 1 (one character).
Without it, length($bytes) returns 2 (two bytes). Same bytes,
different interpretation. The flag is the switch.
When you get garbled text or "Wide character" warnings, dump the variable. If the UTF8 flag is missing when it should be there (or present when it should not be), that is your bug.
Part 6: REFERENCE COUNTING
Every Perl variable has a reference count (REFCNT). It tracks how many things point to this value:When REFCNT hits 0, Perl frees the memory. This is Perl's garbage collection: simple reference counting.use Devel::Peek; my $x = "hello"; Dump($x); # REFCNT = 1 my $ref = \$x; Dump($x); # REFCNT = 2 { my $ref2 = \$x; Dump($x); # REFCNT = 3 } Dump($x); # REFCNT = 2 (ref2 went out of scope)
Circular references break this:
Dump the structures and you can see the REFCNT never reaching zero. This is whymy $a = {}; my $b = {}; $a->{other} = $b; $b->{other} = $a; # REFCNT of both is 2 # when $a and $b go out of scope, REFCNT drops to 1, not 0 # memory leak!
Scalar::Util::weaken exists.
You can also get just the reference count without a full dump:
use Devel::Peek qw(SvREFCNT); my $x = "hello"; my $ref = \$x; say SvREFCNT($x); # 2
Part 7: ARRAYS AND HASHES
Dump works on arrays and hashes too:use Devel::Peek; my @arr = (1, "two", 3.0); Dump(\@arr);
FILL is the highest used index (2, meaning 3 elements). MAX is the allocated size (Perl over-allocates for growth). Each element is a separate SV with its own type and flags.SV = IV(0x...) at 0x... REFCNT = 1 FLAGS = (TEMP,ROK) RV = 0x... SV = PVAV(0x...) at 0x... REFCNT = 1 FLAGS = () ARRAY = 0x... FILL = 2 MAX = 3 FLAGS = (REAL) Elt No. 0 SV = IV(0x...) at 0x... REFCNT = 1 FLAGS = (IOK,pIOK) IV = 1 Elt No. 1 SV = PV(0x...) at 0x... REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x... "two"\0 CUR = 3 LEN = 10 Elt No. 2 SV = NV(0x...) at 0x... REFCNT = 1 FLAGS = (NOK,pNOK) NV = 3
For hashes, you can see the bucket structure:
This shows the hash internals, including key-value pairs and the underlying hash table structure.my %h = (name => "Larry", age => 69); Dump(\%h);
Part 8: PRACTICAL DEBUGGING
Problem: string comparison fails mysteriously.Dump both:my $a = get_from_database(); # returns "hello" my $b = get_from_user_input(); # returns "hello" if ($a eq $b) { say "match"; } else { say "no match"; # this fires. why? }
Dump($a); Dump($b);
One has the UTF8 flag, the other does not. Even though the bytes are identical, Perl compares them differently when the flags disagree. Fix it by normalizing both to the same encoding:SV = PV(0x...) at 0x... FLAGS = (POK,pPOK,UTF8) PV = 0x... "hello"\0 [UTF8 "hello"] SV = PV(0x...) at 0x... FLAGS = (POK,pPOK) PV = 0x... "hello"\0
Or useuse Encode qw(decode encode); $a = decode('UTF-8', encode('UTF-8', $a)); $b = decode('UTF-8', encode('UTF-8', $b));
utf8::upgrade on both.
Problem: number that is not a number.
my $val = "42\n"; # trailing newline from chomp failure say $val + 1; # 43, but with a warning Dump($val);
The PV showsSV = PV(0x...) at 0x... FLAGS = (POK,pPOK) PV = 0x... "42\n"\0 CUR = 3
"42\n". There it is. A hidden newline. The chomp
was missed or failed. Devel::Peek makes invisible characters
visible because it shows escape sequences in the PV dump.
Part 9: DUMPING COMPLEX STRUCTURES
For deep data structures,Dump gets verbose. You can control
the depth:
The second argument limits recursion depth. Without it, Dump follows every reference to the bottom, which can produce thousands of lines for complex structures.use Devel::Peek; my $data = { users => [ { name => "Alice", age => 30 }, { name => "Bob", age => 25 }, ], }; Dump($data, 2); # only go 2 levels deep
For quick overviews, sometimes Data::Dumper is more readable:
Butuse Data::Dumper; say Dumper($data); # human-friendly structure dump
Data::Dumper shows you the Perl-level view. Devel::Peek
shows you the C-level view. When Data::Dumper says two things
are the same but they behave differently, Devel::Peek tells
you why.
They complement each other. Use Data::Dumper first to see the
structure. Use Devel::Peek when something in the structure
does not behave as expected.
Part 10: THE CONVERSION CACHE
The most important insight fromDevel::Peek is understanding
Perl's conversion caching.
use Devel::Peek; my $x = "123.45"; say "Step 1: string only"; Dump($x); my $y = $x + 0; say "Step 2: after numeric use"; Dump($x); my $z = "$x"; say "Step 3: after string use (already cached)"; Dump($x);
Step 1: just a string. Step 2: Perl computed the numeric value and cached it (NV appears alongside PV). Step 3: both representations already existed, no new conversion needed.Step 1: string only FLAGS = (POK,pPOK) PV = "123.45" Step 2: after numeric use FLAGS = (NOK,POK,pNOK,pPOK) NV = 123.45 PV = "123.45" Step 3: after string use (already cached) FLAGS = (NOK,POK,pNOK,pPOK) NV = 123.45 PV = "123.45"
This is why Perl does not re-parse "123.45" every time you use it as a number. The first conversion is cached. Every subsequent use is a direct read from the cached slot.
It also explains a subtle behavior. Once a variable has been used as both a string and a number, it keeps both representations even if you change one:
Assignment clears the stale cache. Perl is not going to serve you yesterday's value from the numeric slot when you changed the string today.my $x = "42"; $x + 0; # cache the IV $x = "99"; # assign a new string value Dump($x); # FLAGS = (POK,pPOK) - IV cache is invalidated # PV = "99" # The old IV = 42 is gone
Perl's internal variable structure: +---------------------------+ | SV (Scalar Value) | +---------------------------+ | REFCNT: 1 | | FLAGS: IOK, POK, UTF8 | +---------------------------+ | IV: 42 (integer) | | NV: 42.0 (float) | | PV: "42\0" (string) | | CUR: 2 (strlen) | | LEN: 10 (bufsize) | +---------------------------+ .--. |o_o | "Same variable. |:_/ | Three values. // \ \ Zero contradictions." (| | ) /'\_ _/`\ \___)=(___/
Devel::Peek is not a tool you use every day. It is a tool you
use when nothing else explains the behavior. When Data::Dumper
says two variables are identical but eq says they are not. When
a number works in one context but not another. When UTF-8 is
sometimes right and sometimes wrong and you cannot figure out why.
Those are Devel::Peek moments. You dump the variable, you see
the flags, you see the cached values, and the mystery evaporates.
It is Perl's MRI machine. You do not use it for a checkup. You use it for a diagnosis.
perl.gg