views:

509

answers:

3

I have the following code, and for the life of me, I cannot understand why there would be an Access Violation exception? I even deleted all the OBJs, TDS etc files and put it into a new project, still the Access Violation occurs.

Essentially, this code displays a TListView in a TFrame and is to show the various current times around the world for different time zones.

Note: The code is in C++ Builder 6.

Can someone help?

BLOODY-HELL-UPDATE: Solved. I should not add items to TListView in the TFrame constructor. DUMB DUMB DUMB.

MAJOR UPDATE: It seems that when the UpdateTimes() is called via the timer, the "li->Deleting" property is TRUE. When called outside the timer, it is FALSE. Now why would "li->Deleting" be set to 'true' because it is called from the timer? If i do:

if(li->Deleting == false)
{
  li->Caption = "abcd";
}

It doesnt enter the if(), when UpdateTimes() is called from the timer...... argggggh!!!

UPDATE: It seems like if I call UpdateTimes() outside of the TTimer, it works fine. But when called from the timer, it throws the Access Violation. What gives?

Header File:

#ifndef CurrentTimes_FrameH
#define CurrentTimes_FrameH
#include <Classes.hpp>
#include <Controls.hpp>
#include <StdCtrls.hpp>
#include <Forms.hpp>
#include <ExtCtrls.hpp>
#include <ComCtrls.hpp>
#include <list>
using namespace std;
//---------------------------------------------------------------------------
struct LOCATIONTIMEINFORMATION
{
  AnsiString TimeZoneName;
  AnsiString PlaceName;
  int    UtcOffsetMinutes;
  TListItem* ListItem;
};
//---------------------------------------------------------------------------
class TCurrentTimesFrame : public TFrame
{
__published:    // IDE-managed Components
    TTimer *Timer;
    TListView *ListView;
    void __fastcall TimerTimer(TObject *Sender);
private:    // User declarations
public:     // User declarations
    __fastcall TCurrentTimesFrame(TComponent* Owner);
//---------------------------------------------------------------------------
//User Code
//---------------------------------------------------------------------------
private:
    list<LOCATIONTIMEINFORMATION>   FTimeInformation;
  typedef list<LOCATIONTIMEINFORMATION>::iterator LocationTimeInformationItr;
public:
  void AddTimeInformation(LOCATIONTIMEINFORMATION lti);
  void UpdateTimes();
};
//---------------------------------------------------------------------------
#endif

CPP File:

#include <vcl.h>
#pragma hdrstop
#include "CurrentTimes_Frame.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
//---------------------------------------------------------------------------
__fastcall TCurrentTimesFrame::TCurrentTimesFrame(TComponent* Owner): TFrame(Owner)
{
  Timer->Enabled = false;
  <strike>{
    LOCATIONTIMEINFORMATION lti;
    lti.TimeZoneName = "UTC";
    lti.PlaceName = "Near Greenwich, England";
    lti.UtcOffsetMinutes = 0;
    AddTimeInformation(lti);
  }</strike>
  //UPADTED: Don't add TListItem from constructor 
}
//---------------------------------------------------------------------------
void TCurrentTimesFrame::AddTimeInformation(LOCATIONTIMEINFORMATION lti)
{
  TListItem* li = ListView->Items->Add();
  li->Caption = lti.TimeZoneName;
  li->SubItems->Add(lti.PlaceName);
  li->SubItems->Add(lti.UtcOffsetMinutes);
  li->SubItems->Add("<time will come here>");
  lti.ListItem = li;
  ShowMessage(AnsiString(lti.ListItem->ClassName())); //Correctly shows "TListItem"
  FTimeInformation.push_back(lti);

  {
  LOCATIONTIMEINFORMATION temp = FTimeInformation.front();
  ShowMessage(AnsiString(temp.ListItem->ClassName())); //Correctly shows "TListItem"
  }
  Timer->Enabled = true;
}
//---------------------------------------------------------------------------
void __fastcall TCurrentTimesFrame::TimerTimer(TObject *Sender)
{
    UpdateTimes();
}
//---------------------------------------------------------------------------
void TCurrentTimesFrame::UpdateTimes()
{
  Timer->Enabled = false;
  TListItem* li;
  for(LocationTimeInformationItr itr=FTimeInformation.begin();itr!=FTimeInformation.end();itr++)
  {
    li = itr->ListItem;

    ShowMessage(AnsiString(li->ClassName())); //Access Violation:
    /*
    ShowMessage() above shows:

    ---------------------------
    Debugger Exception Notification
    ---------------------------
    Project XX.exe raised exception class EAccessViolation with message 'Access violation at address 4000567D in module 'rtl60.bpl'. Read of address 00000000'. Process stopped. Use Step or Run to continue.
    ---------------------------
    OK   Help
    ---------------------------
    */
  }
  Timer->Enabled = true;
}
//---------------------------------------------------------------------------

UPDATE A sample code demo'ing that list takes items as copy, not reference. (As far as I can see, please correct me if im making some mistake in the code below)

@Craig Young:

I'm confused... I thought structs would be added to the list as a copy not as a reference? Please take a look at the code below, it seems that a copy is being made? Or am I missing something rudimentary? Or a coding mistake below??

void PopulateData()
{
    AnsiString DebugText;
    list<LOCATIONTIMEINFORMATION> Data;

  LOCATIONTIMEINFORMATION OnStack;

  //Prints "junk"
  DebugText.sprintf("%s,%s,%d,%d",OnStack.TimeZoneName,OnStack.PlaceName,OnStack.UtcOffsetMinutes,(int)OnStack.ListItem);

    OnStack.TimeZoneName = "UTC";
    OnStack.PlaceName = "Near Greenwich, England";
    OnStack.UtcOffsetMinutes = 10;
    OnStack.ListItem = (TListItem*)20;

  //OnStack:
  DebugText.sprintf("%s,%s,%d,%d",OnStack.TimeZoneName,OnStack.PlaceName,OnStack.UtcOffsetMinutes,(int)OnStack.ListItem);
  //Add data to list
    Data.push_back(OnStack);

  //Get struct from list
  LOCATIONTIMEINFORMATION InList = Data.front();

  //OnStack:
  DebugText.sprintf("%s,%s,%d,%d",OnStack.TimeZoneName,OnStack.PlaceName,OnStack.UtcOffsetMinutes,(int)OnStack.ListItem);
  //InList:
  DebugText.sprintf("%s,%s,%d,%d",InList.TimeZoneName,InList.PlaceName,InList.UtcOffsetMinutes,(int)InList.ListItem);

  //Change OnStack
    OnStack.TimeZoneName = "NONE";
    OnStack.PlaceName = "USA";
    OnStack.UtcOffsetMinutes = 50;
    OnStack.ListItem = (TListItem*)90;

  //OnStack:
  DebugText.sprintf("%s,%s,%d,%d",OnStack.TimeZoneName,OnStack.PlaceName,OnStack.UtcOffsetMinutes,(int)OnStack.ListItem);
  //InList:
  DebugText.sprintf("%s,%s,%d,%d",InList.TimeZoneName,InList.PlaceName,InList.UtcOffsetMinutes,(int)InList.ListItem);

  //Change InList:
    InList.TimeZoneName = "SOME";
    InList.PlaceName = "BRAZIL";
    InList.UtcOffsetMinutes = 66;
    InList.ListItem = (TListItem*)88;

  //OnStack:
  DebugText.sprintf("%s,%s,%d,%d",OnStack.TimeZoneName,OnStack.PlaceName,OnStack.UtcOffsetMinutes,(int)OnStack.ListItem);
  //InList:
  DebugText.sprintf("%s,%s,%d,%d",InList.TimeZoneName,InList.PlaceName,InList.UtcOffsetMinutes,(int)InList.ListItem);
}
A: 

I can't actually see any problem with the code.

Try printing the TimeZoneName or PlaceName from your iterator, rather than li->ClassName(), to make sure you haven't accidentally added something else to the list or something...

Artelius
Doing ShowMessage(itr->PlaceName);works.That means that itr->ListItem is "ruined" somewhere?
Liao
So, the problem was infact my bad. Not to ever ever ever add TListItem and suchlike in the ctor.
Liao
A: 

What values do you have in FTimeInformation? For example, is li == NULL?


If we can assume that the access violation occurs the first time through the loop, and li points to a valid TListItem then perhaps we should split the three statements on the line over three lines. Something like this:

const char* className = li->ClassName();
AnsiString  ansiString(className);
ShowMessage(ansiString);

If the access violation doesn't happen on the first line this will tell us something interesting.

richj
No, li is not null, it shows a valid pointer.
Liao
No luck with that. See my "UPDATE:" in the question. Basically, the access violation is only occuring when UpdateTimes() is called from within the timer.
Liao
Does the timer use a separate thread? If it does, then access to shared values should be made in a thread-safe way, because otherwise there is no guarantee that changes made in one thread are visible to another.
richj
No separate thread.. please see new 2nd "UPDATE:" in my question
Liao
richj
+3  A: 

EDIT: My answer is incorrect, I've decided to leave it in place because it is worthwhile bearing in mind that if your collection (list) holds elements by reference, this is a very real possibility for 'strange access violations'. The symptoms described would correlate perfectly had it not been for the STL list keeping a copy of the elements.

Hi Liao,

You wrote: "BLOODY-HELL-UPDATE: Solved. I should not add items to TListView in the TFrame constructor."

I'm going to disagree with you; you have not solved it. While not necessarily a good idea (in terms of design), adding items to TListView in the TFrame constructor should not cause access violations.

EDIT: Despite my answer below being incorrect, I still disagree with Liao's 'BLOODY-HELL-UPDATE'. Adding items to TListView in the TFrame constructor should not cause access violations. In fact I took the original code and tested it in CPBB 2009, and it worked perfectly. This suggests that the error may have been in how the frame was used; or some other aspect of the code that was not demonstrated.

The problem is with the following line in the constructor:

LOCATIONTIMEINFORMATION lti;
  • This allocates lti on the stack.
  • You then add lti to a list; or more correctly: you add a reference to lti to the list.
  • When your constructor goes out of scope, so does lti; and that memory can be reused by any other part of your application.
  • Later when your timer attempts the update, the reference in FTimeInformation is still there.
  • You use this reference to lookup where lti was.
  • If that section of memory has been changed by any other part of your application, then ltr->ListItem no longer references the TListItem that was created in the constructor. Instead it references some other part of memory that it tries to use as if it were a TListItem. Therefore you experience 'strange' problems such as:
    • li->Deleting == false
    • li->ClassName causing an access violation.

NOTE: Whether or not you actually get an access violation usually depends a bit on luck: Consider yourself lucky if you do get the access violation; the other option is usually 'inexplicable' erratic behaviour.

Try modifying your constructor as follows, it should fix the access violation. NOTE: lti is now dynamically allocated, you'll have to decide when to free it, otherwise you'll have a memory leak ;)

LOCATIONTIMEINFORMATION* lti = new LOCATIONTIMEINFORMATION;
lti->TimeZoneName = "UTC";
lti->PlaceName = "Near Greenwich, England";
lti->UtcOffsetMinutes = 0;
AddTimeInformation(*lti);
Craig Young
+1 good diagnosis
0A0D
You said that "You then add lti to a list; or more correctly: you add a reference to lti to the list." But I'm not sure thats correct..I added a code sample at the end of the question (it would not fit in the comment which is max 600 chars). The code sample seems to show that list.push_back adds a *copy* of the struct, not a reference. Could you run that and see?
Liao
-10 for incorrect diagnosis. push_back() in (at least) a STL list, adds a *copy*, not a *reference* of the struct. Only while *accessing* the STL list items, it does return references.
Liao
You're right; my answer is incorrect. In fact I've plugged your original code into CPPB 2009, dropped the frame on a form, and it worked perfectly. This suggests that your error was not in the code you demonstrated, but perhaps in how you're using the frame?
Craig Young

related questions