Consider the following code:
procedure Test;
var
MyPCharArray: array of PChar;
begin
SetLength(MyPCharArray, 3);
GetMem(MyPCharArray[0], 5);
GetMem(MyPCharArray[1], 5);
GetMem(MyPCharArray[2], 5);
StrCopy(MyPCharArray[0], 'test');
StrCopy(MyPCharArray[1], 'abcd');
StrCopy(MyPCharArray[2], '1234');
// Are these necessary?
FreeMem(MyPCharArray[0], 5);
FreeMem(MyPCharArray[1], 5);
FreeMem(MyPCharArray[2], 5);
end;
Should the allocated elements be freed manually, or the compiler will free the array elements automatically when MyPCharArray goes out of scope?
Indeed each of your calls to GetMem must be matched with a call to FreeMem.
I'm not sure why you have this array. It's not a type that I'd expect to see in pure Pascal code. So my guess is that you are passing the PChar array to some external library. In which case I'd declare an array of string as well as the array of PChar. And then make each PChar element by using PChar(...) on the corresponding element of the string array. Then you can avoid the StrCopy, GetMem and FreeMem.
procedure CallLib(const str: array of string);
var
i: Integer;
parr: array of PChar;
begin
SetLength(parr, Length(str));
for i := 0 to high(parr) do
parr[i] := PChar(str[i]);
// call library now
end;
Related
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.
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.
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;
I have a static array:
myArray: array [0..15] of byte;
I wish to pass this array (not a copy of the array) into a function. Inside the function, I will manipulate the data in array.
Different lengths of STATIC byte array may be passed in to Init at different times. Therefore, I do not wish to declare Init to be able to receive only a certain length of byte array. (e.g. in this case it is a 16 bytes. At other times it may be 65000 bytes)
This is the way I used the function:
Init(#myArray[0]); //passing in pointer to static array
I tried to declare Init like this:
procedure Init(x: array of byte);
begin
//Do some stuff with x, for e.g.
Length(x); //I do not intend to pass in the length as a parameter,
//but determine it in this function itself.
end;
Is this also a correct way of declaring the Init function parameter?
Instead of Init(#myArray[0]) use Init(myArray). Delphi will transfer the myArray by reference
You declared x as open array of bytes. The open array parameters are type compatible with the array variables with the same element type. Delphi will transfer fixed array length as a hidden parameters. You can access it with Length(x) and e.g. for i := Low(x) to High(x) do.
Open array parameters cannot be set to nil. If you need to set x to nil, then you have to declare x as pointer to Byte or pointer to array[..] of Byte.
For static arrays there is no information about their length at runtime, but only at compile time.
So if you loose the type information at passing a static array to a function, there is no way to find out the length of the array within the function.
=> If you want to use static arrays with variable sizes, you must pass the size as additional parameter.
The clean way would be to work with pointer aritmetic rather than arrays here.
PByte = ^byte;
procedure Init(arr: PByte; len: Integer);
var
i: Integer;
begin
//Write 0, 1, 2 ... to arr
for i:= 0 to len-1 do
begin
arr^:= i;
Inc(arr);
end;
end;
Unclean solution:
TByteArray = array [0..$FFFFFFFF] of Byte;
PByteArray = ^TByteArray;
procedure Init(arr: PByteArray; len: Integer);
begin
arr^[0]:= 0;
arr^[1]:= 1;
...
end;
...
var
myAray: array[0..10] of byte;
begin
Init(PByteArray(#myArray[0]), 10);
end;
First, declaring
procedure Init(x: array of byte);
declare an open array parameter (not a dynamic array, unlike some other stated).
To do what you want, you need to declare like this :
type
TMyArray: array [0..15] of byte;
var
MyArray : TMyArray;
Procedure Init(var X : TMyArray)
Still, I'm not sure why you try to set the variable to nil. A static array variable is a little bit like a record, you can't set it to nil. And IF you pass a pointer to the function instead, you will need to assign your array to a variable first anyway(or allocate the memory directly in the function). Like this :
type
TMyArray : Array[0..15] of byte;
pTMyArray : ^TMyArray;
Procedure Init(var X : pTMyArray);
procedure DoSomething;
var P : pTMyArray;
begin
New(P); //or if you array is global, simply do "P := #MyArray"
try
//code here
Init(P);
//code here
finally
Dispose(P);
end;
end
But that would be a bad construct IMO, because if you set your array pointer to nil inside the Init function, you will get a memory leak. So this is pretty bad to start with. But I guess if it's your Init function that reserve the memory that would be OK.
If you settle for an open array parameter, I'd suggest using the Low and High to determine the bounds of the array.
I think that's the best info I can give you until you give more detail on what you are trying to achieve.
EDIT
Ok, if you need the function to accept an array of different size, you need an open array parameter.
You did it right in your exemple. But you can't set it to nil within the function.
I don't think it's possible to pass to a function a pointer to an open array type. If you really want a pointer to set it to nil, you will need to pass it as PByteArray... But you will need to also pass the size of the array.
first when your are declarating this
procedure Init(x: array of byte);
you are using a dynamic array open array parameter instead of a static array.
to pass a static array as a parameter , you must define a type
like this
type
Arr15 = array [0..15] of byte;
and then declare the procedure like this, using the keyword var to pass by reference.
procedure Init(var x: Arr15);
about the nil assign , you cannot set to nil a static array.
another alternative is use a dynamic array
check this sample
type
ByteArray = array of byte;
procedure Init(var x : ByteArray);
const
LenArray=15;
var
i : Byte;
begin
SetLength(x,LenArray); //alocate the memory and set the length of the array.
for i:=0 to LenArray-1 do //fill the array
x[i]:=i;
Writeln(Length(x)); //show the len of the array
SetLength(x,0); //now dealocates de memory
Writeln(Length(x)); //show the len of the array again
end;
Var
myArray : ByteArray;
begin
try
Init(myArray);
Readln;
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
I was looking at Delphi: array of Char and TCharArray "Incompatible Types" and started experimenting. What I discovered is rather interesting.
procedure Clear(AArray: array of Integer);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := 0;
end;
var
MyArray: array of Integer;
begin
Clear(MyArray);
end.
This simple little example shows how you can pass a Dynamic Array to a procedure using an Open Array parameter. It compiles and runs exactly as expected.
procedure Clear(AArray: array of Char);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
var
MyArray: array of Char;
begin
Clear(MyArray);
end.
Here is nearly identical code the only difference being it is using an array of Char rather than Integer. It does not compile. Instead the compiler spits out:
E2010 Incompatible types: 'Array' and 'Dynamic array'
Why would this be?
After searching for a while I discovered this QC report. I'm running Delphi 2009 and its still happening.
Since the documentation specifically mentions open array parameters of type Char to be compatible with dynamic arrays, this should be a bug. From 'Open Array Parameters':
function Find(A: array of Char):
Integer; [...] Note: [...] The
previous example creates a function
that takes any array of Char elements,
including (but not limited to) dynamic
arrays. [...]
You can work with this kind of array, defining your own type:
type
TCharDynArray = array of char;
procedure Clear(AArray: TCharDynArray);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
procedure test;
var
MyArray: TCharDynArray;
begin
Clear(MyArray);
end;
This code will compile fine. It doesn't do anything useful of course (the AArray parameter is not set as "var", so it's copied on the stack before assigning a #0 to every item). But at least, it compiles.
In practice, I found out more easy to define or use high-level of types for dynamic arrays (like TIntegerDynArray), because at least it allows you to pass the array as reference, using a var, therefore avoiding to make a copy on stack, and make your code faster.
About the mapping to a PChar, it's usual for all dynamic arrays: you can map a TIntegerDynArray to a pointer, then use it as a PInteger or a PIntegerArray:
procedure AddInteger(var Values: TIntegerDynArray; Value: integer);
var n: integer;
begin
n := Length(Values);
SetLength(Values,n+1);
Values[n] := Value;
end;
procedure Loop(V: PInteger);
begin
if V<>nil then
while V^<>0 do begin
write(V^,' ');
inc(V); // go to next integer in array
end;
end;
var IntArray: TIntegerDynArray;
begin
Loop(pointer(IntArray)); // will display nothing, since pointer(IntArray)=nil for IntArray=[]
AddInteger(IntArray,2);
AddInteger(IntArray,3);
AddInteger(IntArray,0);
Loop(pointer(IntArray)); // will display '2 3 '
end.
The problem is the "array of char" code beeing inconsistent with "array of integer" is certainly in compiler intrinsics, and the fact that a PChar can be type-casted to a string.
I think the reason is that array of Char is compatible with PChar, as this code does compile:
procedure Clear(AArray: array of Char);
var
I: Integer;
begin
for I := Low(AArray) to High(AArray) do
AArray[I] := #0;
end;
var
MyArray: array of Char;
P: PChar;
begin
Clear(P^);
end.
That is probably for historic reasons.
Hopefully Barry Kelly or Danny Thorpe will kick in and provide some more feedback on this.
--jeroen