Del mismo modo, un array es una colección de ligaduras lógicas a valores escalares. Véase la figura 4.1
$a
mantiene
el número de referencias existentes a dicho valor.
Véase la figura 4.2.
Puesto que hay una ligadura entre la variable $a
y
su valor, este contador es al menos uno.
Si este contador desciende hasta cero, Perl
elimina la memoria asignada a ese valor.
Los contadores descienden por diversas razones. Por ejemplo, cada vez que termina un bloque, el contador de los valores asociados con las variables declaradas en ese bloque desciende una unidad. En el caso habitual de que el contador valiera 1, pasará a valer 0 y la memoria asociada con el valor será liberada.
El siguiente ejemplo, debido a Ikegami (PerlMonks), está tomado de una respuesta en PerlMonks. Suponga el siguiente código:
1 my $array_ref = []; 2 for(1 .. 2) { 3 my $hash_ref = { foo => foo, bar => bar }; 4 push(@{$array_ref}, $hash_ref); 5 } 6 7 my $hash_ref = $array_ref->[0]; 8 $array_ref = [];
Estado después de la línea 7:
$array_ref $hash_ref +--------------+ +--------------+ | array ref | | hash ref | +--------------+ +--------------+ | refcount = 1 | | refcount = 1 | +--------------+ +--------------------+ +--------------+ | o=====> | array | | o | +--------------+ +--------------------+ +---------- : -+ | refcount = 1 | : +--------------------+ : | element 0 | : | +--------------+ | : | | hash ref | | : | +--------------+ | : | | refcount = 1 | | V | +--------------+ | +--------------+ | | o========> | hash | | +--------------+ | +--------------+ +--------------------+ | refcount = 2 | | element 1 | +--------------+ | +--------------+ | | keys & vals | | | hash ref | | +--------------+ | +--------------+ | | | refcount = 1 | | | +--------------+ | +--------------+ | | o========> | hash | | +--------------+ | +--------------+ +--------------------+ | refcount = 1 | +--------------+ | keys & vals | +--------------+
Despúes que se sobreescriba la referencia al array (línea 8):
+--------------+ +--------------+ | array ref | | hash ref | +--------------+ +--------------+ | refcount = 1 | | refcount = 1 | +--------------+ +--------------------+ +--------------+ | o | | array | | o | +---------- : -+ +--------------------+ +---------- : -+ : | refcount = 0 | : : +--------------------+ : V | element 0 | : +--------------+ | +--------------+ | : | array | | | hash ref | | : +--------------+ | +--------------+ | : | refcount = 1 | | | refcount = 1 | | V +--------------+ | +--------------+ | +--------------+ | no elements | | | o========> | hash | +--------------+ | +--------------+ | +--------------+ +--------------------+ | refcount = 2 | | element 1 | +--------------+ | +--------------+ | | keys & vals | | | hash ref | | +--------------+ | +--------------+ | | | refcount = 1 | | | +--------------+ | +--------------+ | | o========> | hash | | +--------------+ | +--------------+ +--------------------+ | refcount = 1 | +--------------+ | keys & vals | +--------------+
El array original es liberado automáticamente puesto que su contador de referencias alcanza cero. Las estructuras de los contadores de los elementos alcanzan cero:
+--------------+ +--------------+ | array ref | | hash ref | +--------------+ +--------------+ | refcount = 1 | | refcount = 1 | +--------------+ +--------------+ | o | | o | +---------- : -+ +---------- : -+ : : : : V : +--------------+ +--------------+ : | array | | hash ref | : +--------------+ +--------------+ : | refcount = 1 | | refcount = 0 | V +--------------+ +--------------+ +--------------+ | no elements | | o========> | hash | +--------------+ +--------------+ +--------------+ | refcount = 2 | +--------------+ +--------------+ | keys & vals | | hash ref | +--------------+ +--------------+ | refcount = 0 | +--------------+ +--------------+ | o========> | hash | +--------------+ +--------------+ | refcount = 1 | +--------------+ | keys & vals | +--------------+
Los contadores de referencias de los hashes alcanzan cero y son liberados. Ahora el contador del antiguo segundo elemento alcanza cero:
+--------------+ +--------------+ | array ref | | hash ref | +--------------+ +--------------+ | refcount = 1 | | refcount = 1 | +--------------+ +--------------+ | o | | o | +---------- : -+ +---------- : -+ : : : : V : +--------------+ : | array | : +--------------+ : | refcount = 1 | V +--------------+ +--------------+ | no elements | | hash | +--------------+ +--------------+ | refcount = 1 | +--------------+ | keys & vals | +--------------+ +--------------+ | hash | +--------------+ | refcount = 0 | +--------------+ | keys & vals | +--------------+
y es liberado:
+--------------+ +--------------+ | array ref | | hash ref | +--------------+ +--------------+ | refcount = 1 | | refcount = 1 | +--------------+ +--------------+ | o | | o | +---------- : -+ +---------- : -+ : : : : V : +--------------+ : | array | : +--------------+ : | refcount = 1 | V +--------------+ +--------------+ | no elements | | hash | +--------------+ +--------------+ | refcount = 1 | +--------------+ | keys & vals | +--------------+
Existe un problema con este algoritmo de recolección de basura: las referencias circulares.
En esos casos el programador debe actuar usando, por ejemplo, el operador delete
y las funciones
weaken e isweak en el módulo Scalar::Util.
pp2@nereida:~$ perl -wde 0 main::(-e:1): 0 DB<1> use Scalar::Util qw(weaken isweak) DB<2> $var = "hola\n" DB<3> $ref = \$var DB<4> weaken($ref) DB<5> p isweak($ref) 1 DB<6> $c = $ref DB<7> p "<".isweak($c).">" <>
La función weaken recibe como argumento una referencia. Hace que no se incremente el contador de referencias del valor asociado con dicha referencia. Vea el siguiente ejemplo:
hp@nereida:~/Lperl/src$ cat -n noweaken.pl 1 use strict; 2 use Scalar::Util qw(weaken); 3 my $ref; 4 { 5 my $var = "hola\n"; 6 $ref = \$var; 7 } 8 print $$ref; |
lhp@nereida:~/Lperl/src$ cat -n weaken.pl 1 use strict; 2 use Scalar::Util qw(weaken); 3 my $ref; 4 { 5 my $var = "hola\n"; 6 $ref = \$var; 7 weaken($ref); 8 } 9 print $$ref; |
lhp@nereida:~/Lperl/src$ perl noweaken.pl hola |
hp@nereida:~/Lperl/src$ perl weaken.pl Can't use an undefined value as a SCALAR reference ... |
Una referencia se dice débil (weak) si no cuenta en el objeto al que referencia.
Cuando el contador del objeto al que referencia alcanza cero
la referencia contiene undef
.
Tenga en cuenta que la copia de una referencia débil (creada con weaken
)
es una referencia fuerte:
lhp@nereida:~/Lperl/src$ cat -n weakenwithcopy.pl 1 use strict; 2 use Scalar::Util qw(weaken); 3 my ($ref, $copy); 4 { 5 my $var = "hola\n"; 6 $ref = \$var; 7 weaken($ref); 8 $copy = $ref; 9 } 10 print $$ref; 11 lhp@nereida:~/Lperl/src$ perl weakenwithcopy.pl hola
La función isweak retorna TRUE
si la expresión pasada como
parámetro es una referencia débil.
lhp@nereida:~/Lperl/src/testing$ cat -n ciclos.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use Devel::Cycle; 4 my $test = { 5 fred => [qw(a b c d e)], 6 ethel => [qw(1 2 3 4 5)], 7 george => {martha => 23, agnes => 19} 8 }; 9 $test->{george}{phyllis} = $test; 10 $test->{fred}[3] = $test->{george}; 11 $test->{george}{mary} = $test->{fred}; 12 find_cycle($test); 13 exit 0; lhp@nereida:~/Lperl/src/tAl ejecutar produce una salida como esta:
lhp@nereida:~/Lperl/src/testing$ ./ciclos.pl Cycle (1): $A->{'fred'} => \@B $B->[3] => \%C $C->{'mary'} => \@B Cycle (2): $A->{'fred'} => \@B $B->[3] => \%C $C->{'phyllis'} => \%A Cycle (3): $A->{'george'} => \%C $C->{'mary'} => \@B $B->[3] => \%C Cycle (4): $A->{'george'} => \%C $C->{'phyllis'} => \%A
El módulo permite comprobar la existencia de ciclos con referencias débiles mediante la función find_weakened_cycle. El programa:
lhp@nereida:~/Lperl/src/testing$ cat -n ciclosweaken.pl 1 #!/usr/local/bin/perl -w 2 use strict; 3 use Scalar::Util qw(weaken); 4 use Devel::Cycle; 5 my $test = { 6 fred => [qw(a b c d e)], 7 ethel => [qw(1 2 3 4 5)], 8 george => {martha => 23, agnes => 19} 9 }; 10 $test->{george}{phyllis} = $test; 11 $test->{fred}[3] = $test->{george}; 12 $test->{george}{mary} = $test->{fred}; 13 weaken($test->{george}->{phyllis}); 14 find_weakened_cycle($test); 15 exit 0;
Produce la salida:
lhp@nereida:~/Lperl/src/testing$ ciclosweaken.pl Cycle (1): $A->{'fred'} => \@B $B->[3] => \%C $C->{'mary'} => \@B Cycle (2): $A->{'fred'} => \@B $B->[3] => \%C w-> $C->{'phyllis'} => \%A Cycle (3): $A->{'george'} => \%C $C->{'mary'} => \@B $B->[3] => \%C Cycle (4): $A->{'george'} => \%C w-> $C->{'phyllis'} => \%A
Estudie la siguiente ejecución con el depurador. El módulo Devel::Peek nos permite observar la estructura interna de las variables en el intérprete:
lhp@nereida:~/Lperl/src$ perl -MDevel::Peek -wd weakenwithcopy.pl main::(weakenwithcopy.pl:3): my ($ref, $copy); main::(weakenwithcopy.pl:4): { DB<1> c 10 main::(weakenwithcopy.pl:10): print $$ref; DB<2> - 1: use strict; 2: use Scalar::Util qw(weaken); 3: my ($ref, $copy); 4 { 5: my $var = "hola\n"; 6: $ref = \$var; 7: weaken($ref); 8: $copy = $ref; 9 } 10==> print $$ref; DB<2> Dump $ref SV = RV(0x817b328) at 0x823a050 REFCNT = 2 FLAGS = (PADBUSY,PADMY,ROK,WEAKREF) RV = 0x814f7e0 SV = PVMG(0x83dcb80) at 0x814f7e0 REFCNT = 1 FLAGS = (PADBUSY,PADMY,RMG,POK,pPOK) IV = 0 NV = 0 PV = 0x8238fa8 "hola\n"\0 CUR = 5 LEN = 8 MAGIC = 0x83e3368 ............................. DB<3> Dump $copy SV = RV(0x817b324) at 0x814f810 REFCNT = 2 FLAGS = (PADBUSY,PADMY,ROK) RV = 0x814f7e0 SV = PVMG(0x83dcb80) at 0x814f7e0 REFCNT = 1 FLAGS = (PADBUSY,PADMY,RMG,POK,pPOK) IV = 0 NV = 0 PV = 0x8238fa8 "hola\n"\0 CUR = 5 LEN = 8 MAGIC = 0x83e3368 .............................
El siguiente ejemplo muestra como los elementos de un array tienen sus propios contadores de referencias:
DB<1> @a = 1..5 DB<2> $ra = \$a[4] DB<3> pop @a DB<4> p @a 1234 DB<5> p $$ra 5la orden
pop @a
rompe la ligadura entre $a[4]
y el valor, esto
es, la ligadura-variable $a[4]
es eliminada del array.
Sin embargo el valor asociado no es eliminado de memoria ya
que su contador de referencias todavía no es cero. La variable $ra
aún esta referenciándolo.