Dynamic Arrays and pointers in Delphi - delphi

How can i rewrite this C++ code in Delphi?
int *intim = new int[imsize];
unsigned char *grays = new unsigned char[imsize];
int *intim2 = intim;
How can I increment pointer like this:
*(intim++) = x;

In Delphi you can use pointers (like in C/C++) but usually you try to avoid it.
the code looks most like
uses
Types;
procedure Test();
var
intim: TIntegerDynArray;
grays: TByteDynArray;
P: PInteger;
i, s: Integer;
begin
// 'allocate' the array
SetLength(intim, imsize);
SetLength(grays, imsize);
// if you want to walk through the array (Delphi style)
for i := Low(intim) to High(intim) do intim[i] := GetValueFromFunction();
// or (read only access, sum the array)
s := 0;
for i in intim do Inc( s, i );
// or with pointers:
P := #intim[ 0 ];
for i := 0 to High(intim) do begin
P^ := x;
Inc( P ); // like P++;
end;
end;

The people who point out that you should use array types rather than a direct pointer manipulation done as you see it done above in C, are right, it is not idiomatic to Delphi to use dangerous pointer types when safe arrays are easier, can be verified faster, and are safer at runtime. However for the pedantic out there who want to avoid using the nice built in array types, it is possible, albeit stupid, to do so:
var
intim,intim2:PInteger;
x:Integer;
begin
x := 0;
intim := AllocMem(SizeOf(Integer)*imsize);
intim2 := intim;
// dereference pointer intim2, store something, then increment pointer
intim2^ := x;
Inc(intim2);
FreeMem(intim);

The best way would be to use arrays. If imsize is a constant, you want a static array, otherwise, you'll use a dynamic array. Here's the syntax for both:
Static:
var
intim: array[0..imsize - 1] of integer;
Dynamic:
var
intim: array of integer;
begin
setLength(intim, imsize);
//do something with intim
end;
As for grays, how you'll declare it depends on if you're using your array of "unsigned chars" as characters (a string) or as single-byte integers. If they're integers, you can declare an unsigned single-byte integer as byte, and declare an array (either static or dynamic) of them using the syntax described above. If they're characters, just use the string type.
And the pointer math is possible, but not recommended because it makes buffer overruns too easy. Instead, try declaring your other variable as an integer and use it as an index into the array. If you have bounds checking turned on, this will prevent you from going beyond the end of the array and corrupting memory.

Related

Typecasting dynamic array over static one in Delphi

Q: Is it safe to cast dynamic array over a static one ?
type
Bytearray = array of byte;
function calcCRC(buf1: pointer) : dword;
var
buf: ByteArray ;
outbuffer : array [1..30] of byte;
begin
buf := bytearray(buf1^); // <- is it safe ?
outbuffer[1] := buf[0];
end;
procedure test;
var
testarr : array [1..30] of byte ;
begin
calccrc(#testarr);
end ;
Such snippet compiles and works correctly, even in quite large program with FastMM4. But something tells me, that it can be dangerous ...
Any advice ?
I guess you need to revert your logic. Casting a static array into a dynamic array is not safe at all (it has a counter and reference count before the actual data), whereas the opposite - casting a dynamic array into a static array pointer - is perfectly safe, if you take care of the buffer length.
Your initial code will trigger some random GPF (Access Violations) due to the fact that the reference counter will be modified in calcCRC() in some hidden code - look at the ASM using F2: there is more than you may expect, especially a call DynArrayClear which is pretty unsafe. You didn't have any problem yet because of what was in your memory. But if e.g. a 1 integer is stored before the data, it would trigger a GPF, since leaving calcCRC() would let the Delphi RTL try to release the dynamic array instance.
If you expect to access bytes of memory using indexes, you need to use a pointer to a static array, not a dynamic array.
The code could be the following:
Type
TByteDynArray = array of byte ;
TByteArray = array[0 .. (maxInt div sizeof(byte)) - 1] of byte;
PByteArray = ^TByteArray;
function calcCRCptr(buf1: PByteArray; buf1len: integer): dword;
var
outbuffer : array [1..30] of byte;
begin
result := 0;
// ensure you don't create any access violation by using big indexes
if buf1len < 1 then
exit; // avoid GPF
outbuffer[1] := buf1[0];
...
end;
function calcCRCdynarray(const buf1: TByteDynArray): dword;
begin
// you can use length(buf1) to get the number of items/bytes
result := calcCRCptr(pointer(buf1), length(buf1));
end;
procedure test ;
var
testdynarr: TByteDynArray;
teststaticarray: array[0..10] of byte;
begin
Setlength(testdynarr, 100);
calccrcdynarray(testdynarr) ; // safe
calccrcptr(pointer(testdynarr), length(testdynarr)); // direct call
calccrcptr(#teststaticarray, 11); // OK
end;
Also ensure that you don't mess with pointers, e.g. that you name your variables with proper Delphi convention about values (T...) and pointers (P....). Also follow the same convention to distinguish static arrays and dynamic arrays in your code.
This approach is certainly not safe. The static array has a different memory layout from the dynamic array. The dynamic array has metadata containing reference count and length. You might get away with it in this short excerpt but it is not a technique to be recommended.
Whatever the problem is, this is not the solution. Possible solutions might involve working with open arrays or pointers to byte.

How to convert a null terminated string to string?

I want to store to disk some data records. I want to have each data record of equal size so I can quickly compute and jump to a certain record.
So, I store the only string in my record as an array of chars:
type TFileNameIO = array[1..512] of Char;
After I read the string from disk, the content of my string is like this:
c:\windows#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0#0 etc
I want to truncate the #0 characters, and I do something like this:
function GetFileName(DiskName: TFileNameIO): string;
VAR PC: PChar;
begin
SetString(Result, PChar(#DiskName), Length(DiskName)); <----- here Result is: c:\windows#0#0#0 etc
PC:= PChar(Result);
Result:= WideCharToString(PC);
end;
There is a better way to do it?
You can assign a null-terminated PChar directly to a String:
function GetFileName(DiskName: TFileNameIO): string;
begin
Result := PChar(#DiskName);
end;
Just make sure the TDiskName always contains a null terminator. If it can ever be completely full of characters without a null, you will have to do this instead:
function GetFileName(DiskName: TFileNameIO): string;
var
Len, I: Integer;
begin
Len := 0;
For I := Low(DiskName) to High(DiskName) do
begin
if DiskName[I] = #0 then Break;
Inc(Len);
end;
SetString(Result, PChar(#DiskName), Len);
end;
Your function is not necessary because you can simply assign a character array to a string variable.
type
TCharArray = array [1..512] of Char;
....
var
arr: TCharArray;
str: string;
....
arr := ...;
str := arr;
This results in str being assigned the contents of arr. The compiler implements this with a call to System._UStrFromWArray which first of all looks for a null-terminator. If it finds one, that determines the length of str. Otherwise, the entire contents of the array are copied, and Length(str)=Length(arr).
As a broader point, I would recommend that you change to using zero-based character arrays. From the documentation:
Zero-based character arrays are compatible with PChar and PWideChar. When you use a character array in place of a pointer value, the compiler converts the array to a pointer constant whose value corresponds to the address of the first element of the array.
This makes it easier for you to interop with functions that expect PChar arguments.
As an aside, passing TFileNameIO by value is inefficient because it involves a memory copy. Pass such types by reference. For instance as a const parameter.

Declaring and initializing TVarRec (array of const) params

I want to define a var or const that I can further use in TClientDataSet SetRange method:
var
lRangeStart : array of TVarRec;
lRangeEnd : array of TVarRec;
procedure SetRange;
begin
SetLength (lRangeStart, 2);
SetLength (lRangeEnd , 2);
lRangeStart [0] := Field1; // Incompatible types tVarRect and Integer
lRangeStart [1] := Field2; // Incompatible types tVarRect and Integer
lRangeEnd [0] := Field1; // Incompatible types tVarRect and Integer
lRangeEnd [1] := Field2; // Incompatible types tVarRect and Integer
MyDataSet.SetRange (lRangeStart, lRangeEnd);
end;
That's not the way it works. You create the arrays differently:
MyDataSet.SetRange([Field1, Field2], [Field1, Field2]);
The compiler takes care of generating the various TVarRec records for you.
You can also have mixed types, if the columns in your index are of different types:
MyDataSet.SetRange([1, 'Testing'], [1, 'Testing']);
To use input from the user instead, just assign the the user input to variables, doing any necessary type conversions, and pass in the variables.
var
Val1, Val2: Integer;
begin
Val1 := StrToInt(Edit1.Text);
Val2 := StrToInt(Edit2.Text);
MyDataSet.SetRange([Val1, Val2], [Val1, Val2]);
end;
Or, you can do the conversion in-line, but exceptions due to invalid types are an issue:
MyDataSet.SetRange([StrToInt(Edit1.Text), StrToInt(Edit2.Text)],
StrToInt(Edit1.Text), StrToInt(Edit2.Text)]);
In order to do so yourself, you'd have to explicitly assign to the appropriate type of each element (which defeats much of the purpose of using an array of const, which is being able to create it on-the-fly in your code and mix types):
SetLength(lRangeStart, 2);
lRangeStart[0].vInteger := 1;
lRangeStart[1].vInteger := 2;

Can one initialize multiple variables of some type in one line?

I have been trying to write as less code as possible.
So, I use:
MyBoolean := Memo1.Text <> '';
instead of
if Memo1.Text = '' then
MyBoolean := true
else
MyBoolean := false;
Declare and initialize
var
myGlobal: integer = 99;
to declare and initialize global variable. I would like to do the same for local variables, but seems it is not possible, so, I wonder if there is a way to initialize multiple variables of some type in one line, like in C
int x, y, z;
x=y=z=0;
Thank you.
In C assignment is an expression (returns a value).
In Pascal assignment is a statement (does not return a value).
The difference has some interesting consequences. For example in C both
while (x=0)
and
while (x==0)
are syntactically valid constructions (it is the source of innumerable errors) while in Pascal
while (x:=0)
is syntactically invalid because x:=0 is a statement.
You can only initialize a single local variable per statement.
Of course it would be possible to write a function
function AssignInteger(var _AssignTo: integer; _Value: integer): integer;
begin
Result := _Value;
_AssignTo := _Value;
end;
and use it like this:
var
i, j, k: integer;
begin
i := AssignInteger(j, AssignInteger(k, 5));
But that's not quite what you want, saying you want to write as short as possible code.
I'm pointing this out nonetheless just in case.
As dummzeuch says, you could write your own routine. However, I would prefer one with a signature something like: procedure AssignIntegers(AValue, ATargetArray);. And ideally call the routine with: AssignInteger(99, [X, Y, Z]);.
Unfortunately the ideal option does not work, but the following procedure is close enough and should suffice. It works by taking pointers to the integers that need to be assigned.
procedure AssignIntegers(AValue: Integer; const ATargets: array of PInteger);
var
I: Integer;
begin
for I := Low(ATargets) to High(ATargets) do
ATargets[I]^ := AValue;
end;
The following DUnit test case demonstrates that it works. You can even keep an array of these integer pointers handy to reassign at any time.
type
TArrayPInteger = array of PInteger;
procedure TDelphiTests.TestAssignIntegers;
var
X,Y,Z: Integer;
LGroup: TArrayPInteger;
begin
AssignIntegers(1, [#X, #Y, #Z]); { Pass open arrray using addresses of integers to initialise }
CheckEquals(1, X);
CheckEquals(1, Y);
CheckEquals(1, Z);
LGroup := TArrayPInteger.Create(#X, #Y); { Handy technique to initialise dynamic arrays }
AssignIntegers(2, LGroup);
CheckEquals(2, X);
CheckEquals(2, Y);
CheckEquals(1, Z); { Not part of group }
end;
WARNING
The only real drawback is that you lose type-checking. The compiler won't prevent you from passing the address of non-integer types. This can lead to corrupting other structures' data or access violations.

Can anything bad happen, if I clear my array by assigning a non-initialized array to it in pascal?

In pascal, the only way I dared cleaning my array was to simply iterate through it and clear it, but it is extremely inefficient. Can't I simply reinitialize it by assigning an empty array to it?
program arrays;
var
working, empty : array [1..10] of integer;
begin
working[3] := 5;
working:= empty;
end.
Is is ok to do this, can this backfire?
If you want to clear the array, writing:
working:= empty;
will in fact do the clearing, by copying the empty array content into working... in your case empty is void, since it is a global variable, so initialized with 0.
IMHO it is not a good practice to define such global variables. Global variables are evil in most cases (unless you know what you are doing), and in case of declaring them to be initialized with 0 does not make sense.
In fact, if empty is initialized on the stack (i.e. a var within a method), it is filled with whatever is on the stack at this time, i.e. some random data.
If you want to fast initialize an array which does not contain any reference counted types (like string), you can write:
fillchar(working,sizeof(working),0);
And if your array contains managed types, you can write:
finalize(workingWithStringInside); // to safely release internal managed types
fillchar(workingWithStringInside,sizeof(workingWithStringInside),0);
This is the faster code possible (faster than a variable copy), and sounds a better option.
This is absolutely fine. The semantics of the code are exactly what you need. Certainly the Delphi compiler will emit code to perform a simple and efficient memory copy. The compiler is able to do that because you have a fixed length array whose elements are simple value types. I'd be surprised if FPC did not produce very similar code.
Even if your array contained managed types (it doesn't), the assignment operator would result in code that respected those managed types.
As a final comment, the array full of zeros should be a constant.
An easy way is not to set your length of the variable in the type...and use SetLength to initialize the array for you... from Delphi help: When S is a dynamic array of types that must be initialized, newly allocated space is set to 0 or nil.
type
TIntArray = Array of Integer;
procedure WorkArrays(var aWorking: array of integer);
begin
if High(aWorking) >= 0 then
aWorking[0] := 1;
if High(aWorking) >= 3 then
aWorking[3] := 5;
end;
procedure WorkArrays2(var aWorking: array of integer);
begin
if High(aWorking) >= 1 then
aWorking[1] := 4;
if High(aWorking) >= 9 then
aWorking[9] := 7;
end;
procedure WorkArrays3(var aWorking: TIntArray);
begin
SetLength(aWorking, 0);
SetLength(aWorking, 4);
aWorking[0] := 1;
aWorking[3] := 5;
end;
procedure WorkArrays4(var aWorking: TIntArray);
begin
SetLength(aWorking, 0);
SetLength(aWorking, 10);
aWorking[1] := 4;
aWorking[9] := 7;
end;
procedure TForm58.ShowArrays(aWorking: array of integer);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.ShowArrays2(aWorking: TIntArray);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.Button1Click(Sender: TObject);
var
a_MyArray: array of integer;
a_MyArray1: TIntArray;
begin
//SetLength(aWorking, 0);
SetLength(a_MyArray, 3);//note this is a Zero based Array...0 to 2
WorkArrays(a_MyArray);//note aWorking[3] will not show...because High is 2...
ShowArrays(a_MyArray);
SetLength(aWorking, 0);
SetLength(a_MyArray, 10);//note this is a Zero based Array...0 to 9
WorkArrays2(a_MyArray);
ShowArrays(a_MyArray);
WorkArrays3(a_MyArray1);
ShowArrays2(a_MyArray1);
WorkArrays4(a_MyArray1);
ShowArrays2(a_MyArray1);
end;

Resources