Recent

Author Topic: Range checks and `Move` on different kinds of arrays  (Read 317 times)

Khrys

  • Sr. Member
  • ****
  • Posts: 456
Range checks and `Move` on different kinds of arrays
« on: June 04, 2026, 08:31:03 am »
Suppose I have the following function that uses  Move  to copy an open array to the heap:

Code: Pascal  [Select][+][-]
  1. type
  2.   TVarRecArray = array of TVarRec;
  3.  
  4. function Box(const Items: array of const): TVarRecArray;
  5. begin
  6.   Result := Nil;
  7.   SetLength(Result, Length(Items));
  8.   Move(Items[0], Result[0], Length(Items) * SizeOf(Items[0]));
  9. end;

When enabling range checks, the arguments to  Move  cause problems when  Items  contains no elements, because the compiler (and everyone who doesn't know  Move's  signature, really) doesn't realize that  Items[0]  and  Result[0]  involve no actual memory accesses, but only innocuous address calculations (that never get dereferenced anyway).

Now, my question is: Is there any way to get a reference to the first element of an array for use with  Move  that doesn't trigger range checks and works with all array types?

I don't want to toggle range checks off/on with  {$push}{$R-} [...] {$pop}  for every offending expression, and I don't want to introduce unnecessary branching for code that's perfectly safe.
Pointer casts break in the worst possible way when a dynamic array is changed to a non-dynamic one and vice versa (for reference, in this example  Pointer(@Items)^  would work only because  Items  is an open array; if it were changed to a dynamic array, that expression would cause  Move  to pull undefined data from the stack -  Pointer(Items)^  is what's needed for dynamic arrays).

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12398
  • Debugger - SynEdit - and more
    • wiki
Re: Range checks and `Move` on different kinds of arrays
« Reply #1 on: June 04, 2026, 08:49:25 am »
An open array has in internal pointer too? Did you mean a static array?
But anyway, not the point you ask.

I can't think of many options, other than those you listed. (Technically, I can't think of any other).

You can however try to wrap them away. See if you can write a (inlined) wrapper function, and only do the PUSH/R- in the wrapper. That could be a generic, then it should work for each and every type of array that you have.

Thaddy

  • Hero Member
  • *****
  • Posts: 19247
  • Glad to be alive.
Re: Range checks and `Move` on different kinds of arrays
« Reply #2 on: June 04, 2026, 09:26:09 am »
You can add a simple check:
Code: Pascal  [Select][+][-]
  1. function Box(const Items: array of const): TVarRecArray;
  2. begin
  3.   Result := Nil;
  4.   if high(Items)>=0 then // there are items
  5.   begin
  6.     SetLength(Result, Length(Items));
  7.     Move(Items[0], Result[0], Length(Items) * SizeOf(Items[0]));
  8.   end;
  9. end;
Based on https://www.freepascal.org/docs-html/ref/refsu69.html testit example.

[edit] Tested this and indeed this works: if the parameter is nil, high() returns -1
This follows from the documentation.
« Last Edit: June 04, 2026, 09:59:18 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Khrys

  • Sr. Member
  • ****
  • Posts: 456
Re: Range checks and `Move` on different kinds of arrays
« Reply #3 on: June 04, 2026, 10:00:50 am »
An open array has in internal pointer too? Did you mean a static array?

Pointer(@Items)^  just so happens to work for open arrays too (in addition to static/fixed-size arrays; I can't fathom why, but alas).

See if you can write a (inlined) wrapper function, and only do the PUSH/R- in the wrapper. That could be a generic, then it should work for each and every type of array that you have.

That would probably work, but I had hoped to find some simple expression that just worksTM in every case.

The underlying issue here is really that range checking is too aggressive when it comes to formal parameters in this specific case - of course the compiler can't possibly know that  Move  never actually dereferences invalid pointers here, and the conservative approach it takes is absolutely justified. It's still annoying, though.

Thaddy

  • Hero Member
  • *****
  • Posts: 19247
  • Glad to be alive.
Re: Range checks and `Move` on different kinds of arrays
« Reply #4 on: June 04, 2026, 10:08:26 am »
Just use my code. It works and is documented.
Alternatively a test for any array is simply if High(Array)= -1 the array is empty. Fixed arrays always have elements by definition.
« Last Edit: June 04, 2026, 10:20:41 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Khrys

  • Sr. Member
  • ****
  • Posts: 456
Re: Range checks and `Move` on different kinds of arrays
« Reply #5 on: June 04, 2026, 10:19:17 am »
Just use my code. It works and is documented.

Just read my original post. It documents what I'm actually looking for  :P

I don't want to toggle range checks off/on with  {$push}{$R-} [...] {$pop}  for every offending expression, and I don't want to introduce unnecessary branching for code that's perfectly safe.

Thaddy

  • Hero Member
  • *****
  • Posts: 19247
  • Glad to be alive.
Re: Range checks and `Move` on different kinds of arrays
« Reply #6 on: June 04, 2026, 10:23:04 am »
Well, then I do not fully understand it. moving arrays around, any array, can be checked by checking if it has elements. Both dynamic arrays and arrays of const return -1 if there is nothing to move...fixed array always have elements to move.
As such this prevents rangecheck fails. If there are items you can simply take the address of any array's first element. That is all. So test if there are items is vital. So only if there are items, take the address of array[low(array)] which is the first element of any array type in any dimension that has elements. Else nil.
The check is cheap as well.
« Last Edit: June 04, 2026, 11:18:25 am by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Martin_fr

  • Administrator
  • Hero Member
  • *
  • Posts: 12398
  • Debugger - SynEdit - and more
    • wiki
Re: Range checks and `Move` on different kinds of arrays
« Reply #7 on: June 04, 2026, 11:58:42 am »
See if you can write a (inlined) wrapper function, and only do the PUSH/R- in the wrapper. That could be a generic, then it should work for each and every type of array that you have.

That would probably work, but I had hoped to find some simple expression that just worksTM in every case.
You can probably make the generic even smaller:

Code: Pascal  [Select][+][-]
  1.   generic function ArrayStartAddr<T>(a: T): pointer; inline;
  2.   begin
  3.      {$PUSH}{$R-}{$Q-}
  4.      result := @a[low(a)]; // static array may not start at zero
  5.      {$POP}
  6.   end;

EDIT: may need FPC 3.3.1 and {$ModeSwitch implicitfunctionspecialization}


Similar for the size...

Quote
The underlying issue here is really that range checking is too aggressive when it comes to formal parameters in this specific case

Well, no...
Well, only if you redefine "range check" to "check correct mem access".

The range exists with or without the mem access.

What you are missing here is
- a generic way to get the data address. So you don't need to know about internal pointers => see above generic fuction
- a way to get type info (sizeof) for the array member type

Well actually => you can get typeinfo for the array variable => so you can write code and maybe get the info with the help from that...

But that violates various of your requests, starting with it would need several conditional decisions... And it would not be simple.
« Last Edit: June 04, 2026, 12:05:32 pm by Martin_fr »

Thaddy

  • Hero Member
  • *****
  • Posts: 19247
  • Glad to be alive.
Re: Range checks and `Move` on different kinds of arrays
« Reply #8 on: June 04, 2026, 12:06:38 pm »
well, no you can't use the typeinfo:
Both array of const and fixed length arrays return tkArray. Since they have otherwise different semantics that is not very useful. Dynamic arrays return tkDynArray. But array of const not.
What covers all array types is a length check length(array) > 0 and then the address of array[low(array)]
The test prevents any rangecheck issues. The test is cheap: fixed length is known at compile time otherwise it is a single read operation. (length is stored at negative offset) Low() works for multi-dimensional arrays too.
And much cheaper than typeinfo checking. But be careful with move and arrays, especially dyn arrays where any element index can have a different length (ragged arrays) and memory layout is therefor not consecutive. the slack space needs also copy. In system there are support functions for DynArray.

No need for rangechecks because there is no range issue possible.
« Last Edit: June 04, 2026, 12:52:11 pm by Thaddy »
objects are fine constructs. You can even initialize them with constructors.

Khrys

  • Sr. Member
  • ****
  • Posts: 456
Re: Range checks and `Move` on different kinds of arrays
« Reply #9 on: June 05, 2026, 07:04:37 am »
Quote
The underlying issue here is really that range checking is too aggressive when it comes to formal parameters in this specific case

Well, no...
Well, only if you redefine "range check" to "check correct mem access".

The range exists with or without the mem access.

Ohh, that makes sense. I've been conflating range checks with memory access checking all along  :-[
Thanks for the enlightenment  :D

So only if there are items, take the address of array[low(array)] which is the first element of any array type in any dimension that has elements. Else nil.

Yeah, this is the strictly correct approach. I'd still rather use  {$push}{$R-}{$pop}  though  O:-)

ASerge

  • Hero Member
  • *****
  • Posts: 2496
Re: Range checks and `Move` on different kinds of arrays
« Reply #10 on: June 05, 2026, 05:17:19 pm »
Code: Pascal  [Select][+][-]
  1. type
  2.   TVarRecArray = array of TVarRec;
  3.  
  4. function Box(const Items: array of const): TVarRecArray;
  5. begin
  6.   Result := Copy(TVarRecArray(Pointer(@Items)), 0, Length(Items));
  7. end;

 

TinyPortal © 2005-2018