Recent

Author Topic: Multidimensional arrays as parameters  (Read 1055 times)

jollytall

  • Sr. Member
  • ****
  • Posts: 320
Multidimensional arrays as parameters
« on: March 31, 2024, 01:06:35 pm »
I am a bit confused on how deep a multidimensional array is handed over to a procedure. Let's take the following code:
Code: Pascal  [Select][+][-]
  1. program test;
  2.  
  3. type
  4.   tA = array of array of integer;
  5. var
  6.   A : tA = nil;
  7.  
  8. procedure P1(M : tA);
  9.   var i, j : integer;
  10.   begin
  11.   SetLength(M, 3, 3);
  12.   for i := 0 to 2 do
  13.     for j := 0 to 2 do
  14.       M[i,j] := 100 + i*10 + j;
  15.   end;
  16. procedure P2(var M : tA);
  17.   var i, j : integer;
  18.   begin
  19.   SetLength(M, 3, 3);
  20.   for i := 0 to 2 do
  21.     for j := 0 to 2 do
  22.       M[i,j] := 200 + i*10 + j;
  23.   end;
  24. procedure P3(const M : tA);
  25.   var i,j : integer;
  26.   begin
  27.   SetLength(M[0], 4);
  28.   SetLength(M[1], 4);
  29.   SetLength(M[2], 4);
  30.   for i := 0 to 2 do
  31.     for j := 0 to 3 do
  32.       M[i,j] := 300 + i*10 + j;
  33.   end;
  34.  
  35. var i : integer;
  36.  
  37. begin
  38. SetLength(A, 2, 2);
  39. A[0,0] := 0;
  40. A[0,1] := 1;
  41. A[1,0] := 10;
  42. A[1,1] := 11;
  43. writeln('Original');
  44. for i := 0 to 1 do
  45.   writeln(A[i, 0], ' ', A[i, 1]);
  46. writeln('After P1');
  47. P1(A);
  48. for i := 0 to 1 do
  49.   writeln(A[i, 0], ' ', A[i, 1]);
  50. writeln('After P2');
  51. P2(A);
  52. for i := 0 to 2 do
  53.   writeln(A[i, 0], ' ', A[i, 1], ' ', A[i, 2]);
  54. writeln('After P3');
  55. P3(A);
  56. for i := 0 to 2 do
  57.   writeln(A[i, 0], ' ', A[i, 1], ' ', A[i, 2], ' ', A[i, 3]);
  58. readln;
  59. end.
  60.  

At a first sight it seems logic that after P1 nothing is changed as in that one the parameter is handed over as a value what normally means that the values are copied to the stack and whatever happens with it, it is not reflected any more in the calling place (what might even be just a value, not a variable).

After P2 (where it is handed over as a var parameter, i.e. as a pointer) it is logic that every change done inside the procedure, it is reflected in A as well.

The confusion comes with P3. If it were a normal variable one would expect that M is not allowed to be touched at all inside the procedure as it is a const parameter. I read a lot about it, and it is clear that for a multi-dimensional array the variable is a pointer, and the const protects only the pointer itself, not the content behind it. That is understood, but there are still two strange things with it, what I cannot explain:

1. I cannot change the length of the array itself (SetLength(M, 5) would fail at compile time). If the pointer points to a structure in memory, it should be possible to change a field in the structure, i.e. the number of rows. If I read correctly, it is because the first dimension (pointers to the row arrays) of a matrix is stored as a continuous memory place, and so changing the size of it, might change the memory place (and thus the pointer pointing to it) as well (e.g. if it gets bigger and does not fit in the allocated place). In this case, it should give a run-time error, not a compile time error though. So, it seems that although it might not change the handed over pointer (e.g. if I reduce the size, not increase it) the compiler notices that it is a potentially dangerous operation and does not allow it. However if it is that smart, then why it does not detect the same for the changing of the length of one row, or for the change of elements inside the rows?

2. Accepting that for a multidimensional array the compiler only "cares" about the first level (the rest is hidden behind the pointers stored in the first level array), I do not understand P1. Based on the above, what would be logic is that the compiler makes a copy of the FIRST LEVEL ONLY when the parameter is handed over. So it will have a copy of the list of pointers pointing to the original rows. Then changing the copied array, it would first go to the copied vector of pointers and would find the copy of the pointer, still pointing to the original row. If then I change e.g. the length of the row, then it would make a new row with a new pointer, hence any change of the elements of the changed row would not appear in the original array. However if I do not change the row, only an element of the row (as is the case in the example) then it could use the copied pointer pointing to the original row, and hence change the element of the original row. It is not the case. So, it seems that in this particular case when the array is handed over as a parameter, it makes a deep copy of it (either immediately or when it is touched - being managed). That raises the question, if there is a routine deep inside the compiler to make a deep copy of any shape multi dimensional array, then why is it not available and even claimed by TRon that it does not exist: https://forum.lazarus.freepascal.org/index.php/topic,66313.msg507467.html#msg507467. How is it done then? It would be very useful for other array deep-copy tasks.

What do I miss?

TRon

  • Hero Member
  • *****
  • Posts: 2678
Re: Multidimensional arrays as parameters
« Reply #1 on: March 31, 2024, 01:14:00 pm »
That raises the question, if there is a routine deep inside the compiler to make a deep copy of any shape multi dimensional array, then why is it not available and even claimed by TRon that it does not exist: https://forum.lazarus.freepascal.org/index.php/topic,66313.msg507467.html#msg507467. How is it done then? It would be very useful for other array deep-copy tasks.
Small correction with regards to quoting me: it is not a claim that it does not exist rather a "as far as I know" (as stated in the original message). Ofc with rtti anything is (or might be) possible but afaik there isn't a user space function in the RTL available that performs a deep-copy on multidimensional arrays.

jollytall

  • Sr. Member
  • ****
  • Posts: 320
Re: Multidimensional arrays as parameters
« Reply #2 on: March 31, 2024, 01:40:43 pm »
Sorry for the wrong quote, but usually what you write can be taken as a fact  :D, especially when you also gave a sort-of hint of a reason (because of the nature of dynamic arrays - whatever that means).

TRon

  • Hero Member
  • *****
  • Posts: 2678
Re: Multidimensional arrays as parameters
« Reply #3 on: March 31, 2024, 01:52:39 pm »
Sorry for the wrong quote, but usually what you write can be taken as a fact  :D
Thank you for the confidence boost but do realize that I know next to nothing...

Quote
whatever that means
What I meant with that is that arrays are often mistaken as being flat (and sometimes also mistaken that their memory layout is continuous as well). Arrays are usually used that way, no matter the number of dimensions. Things become more serious when arrays are not used flat and, that includes a deep-copy. The presented solution in that thread from user ASerge sums it up nicely.

Now, if you ask why there isn't a generic (as in general not generics) deep array copy available then I have not the faintest idea. Perhaps because you can already do it in user-space using such a solution as shown by ASerge ...

jollytall

  • Sr. Member
  • ****
  • Posts: 320
Re: Multidimensional arrays as parameters
« Reply #4 on: March 31, 2024, 02:13:10 pm »
Well, almost anything can be done in user-space, but honestly ASerge's solution is really heavy artillery, as he also said. It is definitely not something I routinely write.

However, back to the original question: Can I assume that when a multidimensional array is passed as a value parameter, it is deep copied before use, or do I need to do a deep-copy?

Kays

  • Hero Member
  • *****
  • Posts: 586
  • Whasup!?
    • KaiBurghardt.de
Re: Multidimensional arrays as parameters
« Reply #5 on: April 24, 2024, 07:36:57 pm »
Dynamic arrays are reference counted. After invocation of setLength it is guaranteed that the reference count (of the variable you passed to setLength) is 1. That means using setLength you yourself always create an independent copy inside your routines.

In P3 you cannot invoke setLength directly on M because setLength decreases the reference count in the other previous version of M, i. e. an implicit write operation which is forbidden by const.

A var parameter can be implemented as a value parameter entailing an implicit writeback operation once the routine finishes. This writeback is essentially A_in_main := M_in_P2, i. e. simply copying pointer values (including implicit reference count adjustments). There is no “deep copy” involved.
Yours Sincerely
Kai Burghardt

KodeZwerg

  • Hero Member
  • *****
  • Posts: 2260
  • Fifty shades of code.
    • Delphi & FreePascal
Re: Multidimensional arrays as parameters
« Reply #6 on: April 24, 2024, 08:34:38 pm »
Not much tested but in that direction I guess I would do ...
Code: Pascal  [Select][+][-]
  1. uses Variants;
  2.  
  3. procedure DeepCopyArray(const ASource: Variant; var ATarget: Variant);
  4. var
  5.   i: Integer;
  6.   SourceArray: Pointer;
  7.   TypeInfo: Pointer;
  8. begin
  9.   if VarIsArray(ASource) then
  10.     begin
  11.       DynArrayToVariant(ATarget, SourceArray, TypeInfo);
  12.       for i := VarArrayLowBound(ASource, 1) to VarArrayHighBound(ASource, 1) do
  13.         DeepCopyArray(ASource[i], ATarget[i]);
  14.     end
  15.   else
  16.     ATarget := ASource;
  17. end;
« Last Edit: Tomorrow at 31:76:97 xm by KodeZwerg »

 

TinyPortal © 2005-2018