Perl: How do you dereference an array without creating a copy of the array?
Asked Answered
L

4

7

When I dereference an array using @$arrayRef or @{$arrayRef} it appears to create a copy of the array. Is there a correct way to dereference an array?

This code...

sub updateArray1 {
        my $aRef = shift;
        my @a = @$aRef;
        my $aRef2 = \@a;

        $a[0] = 0;
        push(@a, 3);
        my $aRef3 = \@a;

        print "inside1 \@a: @a\n";
        print "inside1 \$aRef: $aRef\n";
        print "inside1 \$aRef2: $aRef2\n";
        print "inside1 \$aRef3: $aRef3\n\n";
}

my @array = (1, 2);

print "before: @array\n";
my $ar = \@array;
print "before: $ar\n\n";

updateArray1(\@array);

print "after: @array\n";
$ar = \@array;
print "after: $ar\n\n";

... has the output...

before: 1 2
before: ARRAY(0x1601440)

inside1 @a: 0 2 3
inside1 $aRef: ARRAY(0x1601440)
inside1 $aRef2: ARRAY(0x30c1f08)
inside1 $aRef3: ARRAY(0x30c1f08)

after: 1 2
after: ARRAY(0x1601440)

As you can see, @$aRef creates a new pointer address.

The only way I've found to get around this is to only use the reference:

sub updateArray2 {
        my $aRef = shift;

        @$aRef[0] = 0;
        push(@$aRef, 3);

        print "inside2 \@\$aRef: @$aRef\n";
        print "inside2 \$aRef: $aRef\n\n";
}

updateArray2(\@array);

print "after2: @array\n";
$ar = \@array;
print "after2: $ar\n\n";

Which produces the output:

inside2 @$aRef: 0 2 3
inside2 $aRef: ARRAY(0x1601440)

after2: 0 2 3
after2: ARRAY(0x1601440)

Is it possible to dereference a pointer to an array without the whole array getting duplicated? Or do I need to keep it in reference form and dereference it any time I want to use it?

Lawman answered 23/7, 2017 at 6:58 Comment(0)
A
13

Dereferencing does not create a copy as can be seen in the following example:

my @a = qw(a b c);
my $ra = \@a;
@{$ra}[0,1] = qw(foo bar);  # dereferencing is done here but not copying
print @$ra; # foo bar c
print @a; # foo bar c

Instead, assigning the (dereferenced) array to another array creates the copy:

my @a = qw(a b c);
my $ra = \@a;
my @newa = @$ra;   # copy by assigning
$newa[0] = 'foo';
print @newa; # foo b c
print @a; # a b c

Assigning one array to another says essentially that all elements from the old array should be assigned to the new array too - which is different from just having a different name for the original array. But assigning one array reference to another just makes the old array available with a different name, i.e. copy array reference vs. copy array content.

Note that this seems to be different to languages like Python or Java because in these languages variables describe array objects only, i.e. the reference to the array and not the content of the array.

Annitaanniversary answered 23/7, 2017 at 7:48 Comment(0)
C
5

Using the experimental refaliasing feature:

use 5.022;
use warnings;
use feature 'refaliasing';
no warnings 'experimental::refaliasing';
\my @array = $array_ref;

But why not just keep it as a reference? There's nothing you can do with an array that you can't also do with an array reference.

Colettacolette answered 23/7, 2017 at 19:29 Comment(1)
This should be accepted. Unlike everything else, it's the right answer. (I just found out about refaliasing and came here to add it to my answer, but you beat me to it by a mile.)Dubitable
A
3

The code explicitly asks for data to be copied

my @a = @$aRef;

in order to create a new array @a.

It is not clear to me what you mean by

Is it possible to dereference a pointer to an array without the whole array getting duplicated?

If all values are needed in some operation – to make another array, or print them out, or send them to sort or map ... – then data may be copied, or it may not if that is done by pointer. If data is copied (even only on the stack) then it's the same as if we had an array, it is effectively "dereferenced."

It's a question of what is done with data and one cannot say much in general.


If you need to access a specific element (or slice) then derefence that, don't create a new array.

However, please not by @$aRef[0] = 0; even as it happens to be legal, but by either of

$$aRef[0] = 0;
$aRef->[0] = 0;

where I find the second version to be generally safer against silly errors, and clearer.

Alternant answered 23/7, 2017 at 7:54 Comment(3)
a copy is not needed to print them out or send them to sort or mapColettacolette
@Colettacolette No copy? Does it not need to put values on the stack in order for anything to work with them? The map and sort don't get data by pointer -- or do they? I didn't mean to say that a new array is made, just that the data is copied behind the scenes (a new list is made, perhaps element at a time). Thank you for the edit.Alternant
@Colettacolette What I mean to say is that if the values are needed then they are copied, arrayref or not, and "dereferencing" has nothing to do with it. I have an arrayref, but when I submit it to sort the data are made available (on the stack?), just as if I had an array. So it gets "dereferenced." In some other operation it may not -- it depends on what is done with data and one can't say much in general. // If this is non-sense (or wrong) I'd appreciate knowing that. Again, I hope I'm not bothering you, but this relates deeply to many things.Alternant
D
2

My observation/understanding is that it's about the name--if you give a name to the dereferenced array, it will be copied, since perl variables are passed by copy--they're exactly like structs in C++. But even if a reference is copied (this is analogous to int *newPtr = oldPtr in C/C++), each copy of the reference will point at the same object. Only if you access its elements via the reference, or by dereferencing it inline, the original array will be modified. I don't think there's any way around this. When you call my @foo = @{$barRef}, you're calling a copy constructor. Scalars are the same--when you modify an argument via $_[0] = val, the caller's variable is modified as well. But if you give a name to the variable, a la my $arg = $_[0], a copy is made.

That said, there are convenient ways to work with references that you should know about, if you don't already use them:

$arrayRef->[0] = "foo";
$hashRef->{key} = "value";
$codeRef->(); # execute a code reference

But indeed, since push can't deal with a reference, you need to use an inline dereference like you did in your example.

If you really, really want to create an array which is a reference, there's an old perl technique that can do it. You shouldn't use this in modern code. It's described here: https://perldoc.perl.org/perlsub.html#Passing-Symbol-Table-Entries-(typeglobs)

Dubitable answered 23/7, 2017 at 7:30 Comment(1)
Thank you for including the old technique :) Who knows, may come in handy one day. (Probably not lol)Lawman

© 2022 - 2024 — McMap. All rights reserved.