Jump to page: 1 2
Thread overview
Word of warning about passing arrays to functions..
Jun 22, 2005
Derek Parnell
Jun 22, 2005
Jan-Eric Duden
Jun 22, 2005
Regan Heath
Jun 22, 2005
Lionello Lunesu
Jun 22, 2005
Regan Heath
Jun 23, 2005
Andrew Fedoniouk
Jun 23, 2005
Regan Heath
Jun 24, 2005
Oskar Linde
Jun 24, 2005
Regan Heath
Jun 24, 2005
Oskar Linde
Jun 24, 2005
Regan Heath
Jun 24, 2005
Lionello Lunesu
Jun 26, 2005
Regan Heath
June 22, 2005
This was something that bit me.  I thought arrays were passed by reference to functions, like classes.  Hence the whole copy-on-write situation.

Well, they're not.  Sort of.

If you only _modify_ the contents of the array, it's as if you passed it byref (this is more of a programmer's error than anything - a ticking time bomb).  But if you do something that changes the array's .ptr (like resizing it, which may cause the data to be moved), the array in the calling function is left alone.

I was doing this:

void something(char[] s)
{
    // modify s in place
}

And it worked fine.  I could pass in a string and it would be modified in place.  But then I changed it so something() changed the length of the array, and it no longer worked.

I was puzzled, until I added an "inout" to the parameter decl, at which point it worked again.

Just something to remember when you're passing in arrays to functions.


June 22, 2005
Makes perfect sense to me.  I just think of arrays as a struct - pointer and length.  Modify the data pointed to and you modify everyone's copy... modify the pointer and you modify the struct (stack) copy.

-[Unknown]


> This was something that bit me.  I thought arrays were passed by reference to functions, like classes.  Hence the whole copy-on-write situation.
> 
> Well, they're not.  Sort of.
> 
> If you only _modify_ the contents of the array, it's as if you passed it byref (this is more of a programmer's error than anything - a ticking time bomb).  But if you do something that changes the array's .ptr (like resizing it, which may cause the data to be moved), the array in the calling function is left alone.
> 
> I was doing this:
> 
> void something(char[] s)
> {
>     // modify s in place
> }
> 
> And it worked fine.  I could pass in a string and it would be modified in place.  But then I changed it so something() changed the length of the array, and it no longer worked.
> 
> I was puzzled, until I added an "inout" to the parameter decl, at which point it worked again.
> 
> Just something to remember when you're passing in arrays to functions. 
> 
> 
June 22, 2005
On Tue, 21 Jun 2005 21:27:40 -0400, Jarrett Billingsley wrote:

> This was something that bit me.  I thought arrays were passed by reference to functions, like classes.  Hence the whole copy-on-write situation.
> 
> Well, they're not.  Sort of.
> 
> If you only _modify_ the contents of the array, it's as if you passed it byref (this is more of a programmer's error than anything - a ticking time bomb).  But if you do something that changes the array's .ptr (like resizing it, which may cause the data to be moved), the array in the calling function is left alone.
> 
> I was doing this:
> 
> void something(char[] s)
> {
>     // modify s in place
> }
> 
> And it worked fine.  I could pass in a string and it would be modified in place.  But then I changed it so something() changed the length of the array, and it no longer worked.

Well it did work according to the compiler, it just didn't do as you expected.

When 's' is passed, you get a pointer and a length. Your routine modified the data in place, that is it updated the RAM that the pointer points to. But when you change the length, D allocates a new lot of RAM and copies the data to it then sets 's' to the new RAM. But because you passed 's' as an "in" parameter, the changes to it are not passed back to the calling routine. Thus from the perspective of the calling routine, the parameter still points to the original RAM.

The tricky part is that if your function modified the RAM before it changed
the length, the calling routine will see your changes but not the new
length. If you change the length before modifying RAM, the calling routine
will not see your modifications.

> I was puzzled, until I added an "inout" to the parameter decl, at which point it worked again.

Yes, by making the [pointer/length] combination an "inout" parameter then your changes to the length get sent back to the calling routine.

-- 
Derek
Melbourne, Australia
22/06/2005 12:01:14 PM
June 22, 2005
"Derek Parnell" <derek@psych.ward> wrote in message news:194ul3972wdxy.1hekvtrwup6wl$.dlg@40tude.net...
> When 's' is passed, you get a pointer and a length. Your routine modified
> the data in place, that is it updated the RAM that the pointer points to.
> But when you change the length, D allocates a new lot of RAM and copies
> the
> data to it then sets 's' to the new RAM. But because you passed 's' as an
> "in" parameter, the changes to it are not passed back to the calling
> routine. Thus from the perspective of the calling routine, the parameter
> still points to the original RAM.

Oh I know well _why_ it happens, it's just that it wasn't readily apparent that it _was_ happening at first.


June 22, 2005
Derek Parnell wrote:
> On Tue, 21 Jun 2005 21:27:40 -0400, Jarrett Billingsley wrote:
> 
> 
>>This was something that bit me.  I thought arrays were passed by reference to functions, like classes.  Hence the whole copy-on-write situation.
>>
>>Well, they're not.  Sort of.
>>
>>If you only _modify_ the contents of the array, it's as if you passed it byref (this is more of a programmer's error than anything - a ticking time bomb).  But if you do something that changes the array's .ptr (like resizing it, which may cause the data to be moved), the array in the calling function is left alone.
>>
>>I was doing this:
>>
>>void something(char[] s)
>>{
>>    // modify s in place
>>}
>>
>>And it worked fine.  I could pass in a string and it would be modified in place.  But then I changed it so something() changed the length of the array, and it no longer worked.
> 
> 
> Well it did work according to the compiler, it just didn't do as you
> expected.
> 
> When 's' is passed, you get a pointer and a length. Your routine modified
> the data in place, that is it updated the RAM that the pointer points to.
> But when you change the length, D allocates a new lot of RAM and copies the
> data to it then sets 's' to the new RAM. But because you passed 's' as an
> "in" parameter, the changes to it are not passed back to the calling
> routine. Thus from the perspective of the calling routine, the parameter
> still points to the original RAM. 
> 
> The tricky part is that if your function modified the RAM before it changed
> the length, the calling routine will see your changes but not the new
> length. If you change the length before modifying RAM, the calling routine
> will not see your modifications.
>  
> 
>>I was puzzled, until I added an "inout" to the parameter decl, at which point it worked again.
> 
> 
> Yes, by making the [pointer/length] combination an "inout" parameter then
> your changes to the length get sent back to the calling routine.
> 
This is pretty inconsistent behavior if one takes a look at the methods
that modify arrays

if I call method sort of an array in something the change is visible.
if I call method reverse of an array in something the change is visible.
if I set the length property in something the change is not visible.

maybe arrays should always be passed as inout ...
otherwise this behavior only asks for trouble.

Just my 0.2 euro cent....
June 22, 2005
On Wed, 22 Jun 2005 09:31:17 +0200, Jan-Eric Duden <jeduden@whisset.com> wrote:
> Derek Parnell wrote:
>> On Tue, 21 Jun 2005 21:27:40 -0400, Jarrett Billingsley wrote:
>>
>>> This was something that bit me.  I thought arrays were passed by reference to functions, like classes.  Hence the whole copy-on-write situation.
>>>
>>> Well, they're not.  Sort of.
>>>
>>> If you only _modify_ the contents of the array, it's as if you passed it byref (this is more of a programmer's error than anything - a ticking time bomb).  But if you do something that changes the array's .ptr (like resizing it, which may cause the data to be moved), the array in the calling function is left alone.
>>>
>>> I was doing this:
>>>
>>> void something(char[] s)
>>> {
>>>    // modify s in place
>>> }
>>>
>>> And it worked fine.  I could pass in a string and it would be modified in place.  But then I changed it so something() changed the length of the array, and it no longer worked.
>>   Well it did work according to the compiler, it just didn't do as you
>> expected.
>>  When 's' is passed, you get a pointer and a length. Your routine modified
>> the data in place, that is it updated the RAM that the pointer points to.
>> But when you change the length, D allocates a new lot of RAM and copies the
>> data to it then sets 's' to the new RAM. But because you passed 's' as an
>> "in" parameter, the changes to it are not passed back to the calling
>> routine. Thus from the perspective of the calling routine, the parameter
>> still points to the original RAM.  The tricky part is that if your function modified the RAM before it changed
>> the length, the calling routine will see your changes but not the new
>> length. If you change the length before modifying RAM, the calling routine
>> will not see your modifications.
>>
>>> I was puzzled, until I added an "inout" to the parameter decl, at which point it worked again.
>>   Yes, by making the [pointer/length] combination an "inout" parameter then
>> your changes to the length get sent back to the calling routine.
>>
> This is pretty inconsistent behavior if one takes a look at the methods
> that modify arrays
>
> if I call method sort of an array in something the change is visible.
> if I call method reverse of an array in something the change is visible.
> if I set the length property in something the change is not visible.
>
> maybe arrays should always be passed as inout ...
> otherwise this behavior only asks for trouble.

This is another case where enforcing readonly for 'in' parameters and giving a compile time (when possible) or DBC style runtime error for violations would have shown the bug immediately.

It makes sense to be want to modify the contents of an array without modifying the reference, i.e. add 1 to all numbers in an array of integers. This is an example of an array passed with 'in'.

It makes sense to want to append data to an array, modifying the array reference length and possibly the data pointer. This is an example of an array passed with 'inout'.

It makes no sense to pass an array as 'in' and attempt to modify the length and or data pointer. If a modifiable reference is required it's simple enough to create one for that purpose, i.e.

void foo(char[] a)
{
  char[] b = a;
  //it would be a compile time error to modify 'a'.
  //it would be fine to modify 'b'.
}

In cases where a compile time error is not possible ie. the compiler cannot detect the error an implicit DBC check at the end of the function should compare the input array with the local copy, any changes would throw an exception.

As a side issue it also makes sense to want to pass an array whose data and reference are unmodifyable. I thought this wasn't possible, but it seems it now is:

import std.stdio;

const char[] test1 = "testing1";
char[] test2 = "testing2";

void foo(char[] p)
{
	p[0] = 'a';
}

void main()
{
	foo(test1);
	writefln("%s",test1);
	
	foo(test2);
	writefln("%s",test2);
}

[output]
testing1
aesting2

Regan
June 22, 2005
Unknown W. Brackets wrote:

> Makes perfect sense to me.  I just think of arrays as a struct - pointer and length.  Modify the data pointed to and you modify everyone's copy... modify the pointer and you modify the struct (stack) copy.

I like this explanation. It's simple and I'll be keeping it in my mind when dealing with arrays. Thanks.
-- 
Dawid Ciężarkiewicz | arael
June 22, 2005
Shouldn't the compiler complain when passing a const char[] to a
func(char[]) ??

L.

| void foo(char[] p)
| {
| p[0] = 'a';
| }
|
| void main()
| {
| foo(test1);
| writefln("%s",test1);
|
| foo(test2);
| writefln("%s",test2);
| }
|
| [output]
| testing1
| aesting2



June 22, 2005
On Wed, 22 Jun 2005 16:09:28 +0300, Lionello Lunesu <lio@lunesu.removethis.com> wrote:
> Shouldn't the compiler complain when passing a const char[] to a
> func(char[]) ??
>
Indeed, it should, however look at the function declaration:

void foo(char[] p)
{
	p[0] = 'a';
}

'p' is an 'in' parameter. 'in' means "I will read this parameter". So, technically passing a const as 'in' is fine. However, the function goes on to modify the 'in' parameter.

IMO the readonly nature of 'in' needs to be enforced. To do this I would:

1. have a compile time flag for variables signalling whether they are readonly or not, error on incorrect usage (like this one)

2. have a runtime DBC flag for variables signalling whether they are readonly or not (this includes user classes, structs, unions, etc) throw an exception for incorrect usage.

#2 is DBC only so disabled with -release

2 rules are also required:
1. immutable data cannot become mutable
2. mutable data can implicitly be immutable.

Regan
June 23, 2005
"Regan Heath" <regan@netwin.co.nz> wrote in message news:opssslvmy023k2f5@nrage.netwin.co.nz...
> On Wed, 22 Jun 2005 16:09:28 +0300, Lionello Lunesu <lio@lunesu.removethis.com> wrote:
>> Shouldn't the compiler complain when passing a const char[] to a
>> func(char[]) ??
>>
> Indeed, it should, however look at the function declaration:
>
> void foo(char[] p)
> {
> p[0] = 'a';
> }
>
> 'p' is an 'in' parameter. 'in' means "I will read this parameter". So, technically passing a const as 'in' is fine. However, the function goes on to modify the 'in' parameter.
>
> IMO the readonly nature of 'in' needs to be enforced. To do this I would:
>
> 1. have a compile time flag for variables signalling whether they are readonly or not, error on incorrect usage (like this one)

Too many times and too many people were trying to convince Walter to implement const. These efforts has no future.

>
> 2. have a runtime DBC flag for variables signalling whether they are readonly or not (this includes user classes, structs, unions, etc) throw an exception for incorrect usage.

I've tried to do this already. Without opAssign it is just impossible.

>
> #2 is DBC only so disabled with -release
>
> 2 rules are also required:
> 1. immutable data cannot become mutable
> 2. mutable data can implicitly be immutable.
>
> Regan

PS: about RAII: http://www.digitalmars.com/d/archives/7988.html


« First   ‹ Prev
1 2