I have written a program in Delphi to compute, display and save a Pascals' triangle for a user-defined number of rows. It works fine (displays the triangle, and allows me to save it), except that it comes up with an access violation at the end! Here is the message:
Access violation at address 004031DB in module 'Project1.exe'. Read of address 00000000.
I have a 2D dynamic array in the procedure but I release the memory at the end (:= nil). Why is it still giving me an access violation? Very frustrating!
I searched the archives for an answer but could not find an appropriate answer. Any help will be greatly appreciated.
Here is the code (I was a little hesitant as there is a bit of code:
procedure TForm1.btnPTClick(Sender: TObject);
var
I, J, K, N, MidCol: integer;
PT: array of array of integer;
Row: string;
begin
K := StrToInt(lblNumRows.Text);
N := StrToInt(lblNumRows.Text);//# values in row = row number
try
//initiatlize the array
SetLength(PT, K, (N*2)-1);
for I := 0 to K-1 do
for J := 0 to (N*2-1) do
PT[I,J] := 0;
MidCol := (N*2 div 2)-1;//MidCol already 0-based
for I := 0 to K-1 do
begin
if (I = 0) then
PT[I,MidCol] := 1//first row gets 1 in the middle column
else if I = 1 then
begin
PT[I,MidCol-1] := 1;
PT[I,MidCol+1] := 1; //first and last value in second = 1
end
else //if any other row
begin
//Middle column
PT[I, MidCol] := PT[I-1,MidCol-1] + PT[I-1,MidCol+1];
//right triangle
for J := MidCol+1 to (N*2-1) do
begin
if (PT[I-1, J-1]=1) then//if already at the end of prev row
begin
PT[I,J] := 1;
break;
end
else
PT[I,J] := PT[I-1,J-1] + PT[I-1,J+1];
end;
//left triangle
for J := MidCol-1 downto 0 do
begin
if (PT[I-1, J+1] = 1) then //if already at the end of prev row
begin
PT[I,J] := 1;
break;
end
else
PT[I,J] := PT[I-1,J-1] + PT[I-1,J+1];
end;
end;
end;
//now add the pyramid to the memo
Application.ProcessMessages;
for I := 0 to K-1 do
begin
Row := '';
for J := 0 to N*2-1 do
begin
if (PT[I,J] = 0) then Row := Row + ' '
else Row := Row + IntToStr(PT[I,J]);
end;
Memo.Lines.Add(Row);
end;
finally
SetLength(PT, 0, 0);
end;
end;
Read of address 00000000
This indicates that you are trying to access memory using a pointer that is nil. To know why that is so one would need code. At present only you have code, and so only you can explain.
Run the program in the debugger. Enable Debug DCUs in case the error is raised in RTL/VCL code. Ensure that the debugger is configured to break on exceptions. The run your program and trigger the error. The debugger will show you which nil object is being de-referenced. Then you have to work out why that reference is nil.
The code you have added to the answer has a buffer overrun which could certainly explain the problem. Your SetLength is incorrect and should read:
SetLength(PT, K, N*2);
Your code writes to memory out-of-bounds and so corrupts the heap. You should ask the compiler to produce runtime checks on your array bounds. Enable the compiler's range checking option. Had you done so, you would have found this error yourself.
For what it is worth you do not need that try/finally block since the compiler will automatically insert a hidden block. There's no need for two when one suffices. A dynamic array is a managed type whose memory is disposed when the variable leaves scope.
Press F7, to start the project in the debugger.
Look in the main menu for the "Find Error..." option (in Delphi 7 it was under the Search menu)
then enter the address from the exception: 004031DB.
This will show you the exact line where the exception occurred.
Read of address 00000000 generally indicates you are using a pointer that has a nil value.
Related
I have a listview which displays text from a loaded dataset.
I need to remove unwanted words that I listed in a listbox.
How to do that in delphi? I tried to convert the items to a text in the listview but the code didn't work for me..
Here's what I wrote:
var
counter,k : Integer; //counters
begin
counter := 0;
k:=0;
while counter <= listview1.Items.Count do
for k := 0 to Listbox1.items.Count-1 do
if listview1.Items.item[counter].Caption=listbox1.items[k] then
begin
listview1.Items.item[counter].Delete;
inc(counter)
end;
end;
There's multiple things wrong with the code:
You're only incrementing counter when you find a match, hence the
loop will not terminate if you don't.
You're using <= in the head of your while-loop, that will lead to
an Access Violation in the last iteration, since you access
the (n+1)-th element in the ListView with n elements.
If you modify the ListView while iterating over it, you have to
iterate from the back to the front. Suppose you find a match for the
first item of the ListView, you will delete it, and
ListView1.Items[counter] will be the item that was at index counter+1
previously. You can avoid that by changing the order of the iteration
(since deleting an element will not influence the following iterations), and breaking if you find a match.
Also, non-critical, but a question of coding style:
You don't have to initialize loop variables for a for-loop (and the
compiler should have hinted that the value assigned to k in line 2 is
never used, which you shouldn't ignore)
If you have a known number of iterations to perform, as you do for
the outer loop, you usually want to use a for-loop.
Your accessing of the items looks a little weird, though it probably
works.
TL;DR, here's how I would write the code:
procedure TForm1.Button2Click(Sender: TObject);
var counter,k: integer;
begin
for counter := ListView1.Items.Count-1 downto 0 do
for k := 0 to ListBox1.items.Count-1 do
if ListView1.Items[counter].Caption = ListBox1.Items[k] then
begin
ListView1.Items.Delete(counter);
Break;
end;
end;
You increment the outer counter counter in the wrong place. It is easier to code the counter to count backwards when you delete items that are indexed by the counter. Try this:
var
counter,k : Integer; //counters
begin
// counter := 0;
// k:=0;
for counter := listview1.Items.Count-1 downto 0 do
begin
for k := 0 to Listbox1.items.Count-1 do
if listview1.Items.item[counter].Caption=listbox1.items[k] then
begin
listview1.Items.item[counter].Delete;
Break;
end;
end;
end;
I am using DBXJson to parse a simple json file called response.jsonand show it's contents in a grid, but only the first row of the grid ever gets populated with the data and even though there is more rows/data to display. I am using a custom grid in the code below but I have tried a variation of the below code using the standard stringgrid and it exhibited the same behavior. This is the code I am using to parse the response and show it in my grid.
var
sl: TStringList;
LJsonArr: TJSONArray;
LJsonValue: TJSONValue;
LItem: TJSONValue;
col, row: Integer;
begin
col := 0;
row := 0;
sl := TStringList.Create;
sl.LoadFromFile('response.txt');
LJsonArr := TJSONObject.ParseJSONValue(TEncoding.UTF8.GetBytes(sl.text), 0)
as TJSONArray;
for LJsonValue in LJsonArr do
begin
NextGrid1.AddRow();
for LItem in TJSONArray(LJsonValue) do
begin
NextGrid1.Cells[col, row] := TJSONPair(LItem).JsonValue.Value;
inc(col);
end;
inc(row);
end;
sl.Free;
end;
I suspect that the problem lies in the fact that the row variable is out of place and is not getting called and that is causing only the first row to display, but I could be mistaken and I am hoping that a fresh pair of eyes can spot the problem.
The problem is that col must be re-initialised to zero every time you start a new row. So move the initialization of col into the outer loop.
row := 0;
for LJsonValue in LJsonArr do
begin
col := 0;
NextGrid1.AddRow();
for LItem in TJSONArray(LJsonValue) do
begin
NextGrid1.Cells[col,row] := TJSONPair(LItem).JsonValue.Value;
inc(col);
end;
inc(row);
end;
I don't know this JSON library but if it allows you to access array elements with random access then a traditional oindexed for loop would lead to cleaner code that the for in loop that you use. In pseudo code:
for row := 0 to arr.length do
begin
item := arr[row];
for col := 0 to item.length do
grid.Cells[col,row] := item[col];
end;
As a rule of thumb, for in loops are better if you do not need to know the item index. However, as soon as you need to know the item index then traditional indexed for loops are usually to be preferred.
I have a DBGrid and use it to get data into a Array. But as soon as I press the button to do this procedure I get a StackOverFlow error ? Here is the Code I use :
iRy := 0;
iCol := DBGrid.Columns.Count;
sTest := DBGrid.Columns[0].Field.AsString;
While sTest <> '' do
begin
for k := 1 to iCol do
begin
arrData[iRy+1,iCol] := DBGrid.Columns[iCol].Field.AsString;
end;
Inc(iRy);
DBGrid.DataSource.DataSet.Next;
sToets := DBGrid.Columns[0].Field.AsString;
end;
I am using Delphi 7 .
When does sTest change?
A stackoverflow is caused by running out of memory on the stack. This While loop will go forever. You need to set sTest to something other than '' in the loop, or perhaps you meant to use an if statement.
You have a couple of issues:
You never assign anything else to sTest after you initialize it (before the loop)
You're continually looping over the same record, because there's no Next, and no change to sTest. Once you start the loop, you stay there.
You're going past the end of the Columns, because you're going to Column.Count, and the last valid index is Column.Count - 1
Try this instead, to see if it's more what you're after:
iRy := 0;
sCol := DBGrid.Columns.Count - 1;
// I'm not sure why you're not putting this in the first (0 index) element
// of arrData - is that intentional?
sTest := DBGrid.Columns[0].Field.AsString;
while (sTest <> '') and (not DBGrid.DataSource.DataSet.Eof) do
begin
for k := 1 to iCol do
arrData[iRy, iCol] := DBGrid.Columns[iCol].Field.AsString;
DBGrid.DataSource.DataSet.Next;
Inc(iRy);
sTest := DBGrid.Columns[0].Field.AsString;
end;
I am working with Delphi and DirectX. I want an dynamic array of IDirect3DVertexBuffer9. Is it possible? If yes then how?
I have written a code for it. But, it seems to be problematic. My code is shown below -
totalBuffer := 4;
SetLength(g_pVB,totalBuffer);
for cnt := 0 to totalBuffer - 1 do begin
if FAILED(g_pd3dDevice.CreateVertexBuffer(1 * SizeOf(TD3DXVector3),
0, D3DFVF_XYZ,
D3DPOOL_DEFAULT, g_pVB[cnt], nil)) then begin
Result := E_FAIL;
Exit;
end;
if FAILED(g_pVB[cnt].Lock(0, 0, Pointer(pVert[cnt]), 0)) then begin
Result := E_FAIL;
Exit;
end;
pVert[cnt] := 0;
end;
here, the problem I am facing is that, once it enter in for loop it continues and not exit the loop when cnt value is 4. And if I write static value 3 in for loop instead of totalBuffer it will exit the loop when value is 4.
You can find samples here. At Cull sample they used "array of IDirect3DVertexBuffer".
I'm defecting from C# to Delphi 2009, I'm liking it so far very much.
I wrote a binary search procedure, which works fine. I added a simple if-else statement at the end of my proc and it just doesn't fire! I can't see anything wrong with it and am embarrassed to have to say I am stuck. Please help!
procedure BinSearch;
var
min,max,mid, x: integer;
A : array[0..4] of integer;
rslt : integer;
begin
writeln('binary search');
A[0] := 34; A[1] := 65; A[2] := 98; A[3] := 123; A[4] := 176;
listarray(a);
x := 62;
min := 0;
max := 4;
repeat
begin
mid := (min + max) div 2;
if x > A[mid] then
min := mid + 1
else
max := mid - 1;
end;
until (A[mid] = x) or (min > max);
writeln(mid);
writeln(a[mid]);
if A[mid] = x then
rslt := mid
else
rslt := not mid;
if 54 = 65 then
rslt := mid
else
rslt := not mid;
end;
It's the if A[mid] = x then one that won't fire. when debugging neither true or false branches fire, the debugger just skips straight over them. Also the if 54 = 65 then which is just a test does the same.
The if inside my repeat loop works fine though.
If I copy the problem if statement into a mini test proc, and then call the proc it works, so it makes me think it's something else in the proc like a missing ; causing something strange to happen but I cannot see it. Please help!
The Delphi compiler is pretty smart, and it will happily remove unused code. When I compile your code I get compiler hints saying "Value assigned to 'rslt' never used". Since the value is never used, the compiler just skips over those statements.
If you add a Writeln(rslt); to the end of your procedure, you will find that the debugger will now trace through your if statement.
It could be that the debugger is just skipping over those statements even though they are actually running. Make sure that all of the options are turned on in the debugging options. In Delphi 7, they are under Project\Options under the Compiler tab.
The "Begin" statement just after the "Repeat" statement shouldn't be there. A "Repeat" doesn't use a begin. I would remove it just to be sure that it doesn't cause any problems.
"rslt" is not used. Therefore Delphi optimizes it out.
Obviously, you want to return your result. So change your declaration to:
procedure BinSearch(var rslt: integer);
or better, make it a function:
function BinSearch: integer;
and at the end put in:
Result := rslt;
Do either of the above, and you'll find that those statements are no longer skipped over because rslt is now being used.
But, you'll find you will have a problem with your statement:
rslt := not mid;
because mid is an integer. I'm not sure what you want to return here, but I know you don't want the "not" operator to be applied to "mid".
Look at this code that I got from wikibooks. It might help you figure it out.
(* Returns index of requested value in an integer array that has been sorted
in ascending order -- otherwise returns -1 if requested value does not exist. *)
function BinarySearch(const DataSortedAscending: array of Integer;
const ElementValueWanted: Integer): Integer;
var
MinIndex, MaxIndex: Integer;
{ When optimizing remove these variables: }
MedianIndex, MedianValue: Integer;
begin
MinIndex := Low(DataSortedAscending);
MaxIndex := High(DataSortedAscending);
while MinIndex <= MaxIndex do begin
MedianIndex := (MinIndex + MaxIndex) div 2; (* If you're going to change
the data type here e.g. Integer to SmallInt consider the possibility of
an overflow. All it needs to go bad is MinIndex=(High(MinIndex) div 2),
MaxIndex = Succ(MinIndex). *)
MedianValue := DataSortedAscending[MedianIndex];
if ElementValueWanted < MedianValue then
MaxIndex := Pred(MedianIndex)
else if ElementValueWanted = MedianValue then begin
Result := MedianIndex;
Exit; (* Successful exit. *)
end else
MinIndex := Succ(MedianIndex);
end;
Result := -1; (* We couldn't find it. *)
end;