【问题标题】:Crazy Access Violation in C++ Builder 6C++ Builder 6 中的疯狂访问冲突
【发布时间】:2010-07-23 03:52:59
【问题描述】:

我有以下代码,我终其一生都无法理解为什么会出现访问冲突异常?我什至删除了所有的OBJ,TDS等文件并将其放入一个新项目中,仍然发生访问冲突。

本质上,这段代码在 TFrame 中显示了一个 TListView,并显示了世界各地不同时区的各种当前时间。

注意:代码在 C++ Builder 6 中。

有人可以帮忙吗?

血腥地狱更新: 解决了。我不应该在 TFrame 构造函数中向 TListView 添加项目。哑巴哑巴。

重大更新: 似乎当通过计时器调用 UpdateTimes() 时,“li->Deleting”属性为 TRUE。当在定时器外调用时,它是 FALSE。现在为什么将“li->Deleting”设置为“true”,因为它是从计时器调用的?如果我这样做:

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

它没有进入if(),当UpdateTimes()被定时器调用时...... arggggh!!!

更新: 似乎如果我在 TTimer 之外调用 UpdateTimes(),它可以正常工作。但是当从计时器调用时,它会引发访问冲突。什么给了?

头文件:

#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 文件:

#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;
}
//---------------------------------------------------------------------------

更新 演示该列表的示例代码将项目作为副本,而不是参考。 (据我所知,如果我在下面的代码中犯了一些错误,请纠正我)

@Craig Young:

我很困惑...我认为结构会作为 副本 而不是作为参考添加到列表中? 请看下面的代码,好像是在复制?还是我错过了一些基本的东西?还是下面的编码错误??

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);
}

【问题讨论】:

  • 已解决。好的。我学到的是,不要在构造函数中向 TListView 添加项目。多么愚蠢。
  • 不完全;您的“解决方案”可能已经更改了其他内容,但在构造函数中创建 TListItems 不应该固有地导致问题。请查看我的详细回答。
  • 我的错误。我倾向于有更多的德尔福心态;如果没有 STL,我们的 TList 将始终通过引用保存项目,除非另有特别努力。但是,我测试了您的原始代码,它运行良好 - 所以我认为您的错误在其他地方。请发布您如何使用框架。
  • 克雷格,我认为你是对的。问题似乎是当 LOCATIONTIMEINFORMATION 超出范围时调用列表视图的删除事件。我不知道为什么会这样。但是最近,该错误似乎不再发生了。

标签: c++builder access-violation


【解决方案1】:

编辑:我的回答不正确,我决定保留它,因为值得记住的是,如果您的集合(列表)通过引用包含元素,这是一个非常真实的“奇怪的访问违规”的可能性。如果不是 STL 列表保留了元素的副本,所描述的症状将完全相关。

你好廖,

您写道:“BLOODY-HELL-UPDATE:已解决。我不应该在 TFrame 构造函数中向 TListView 添加项目。”

我不同意你的观点;你还没有解决它。虽然不一定是一个好主意(就设计而言),但在 TFrame 构造函数中向 TListView 添加项不应导致访问冲突。

编辑:尽管我在下面的回答不正确,但我仍然不同意廖的“血腥地狱更新”。在 TFrame 构造函数中向 TListView 添加项目不应导致访问冲突。事实上,我采用了原始代码并在 CPBB 2009 中对其进行了测试,并且运行良好。这表明错误可能在于框架的使用方式;或代码中未展示的其他方面。

问题在于构造函数中的以下行:

LOCATIONTIMEINFORMATION lti;
  • 这会在堆栈上分配 lti。
  • 然后将 lti 添加到列表中;或更准确地说:您将 reference 添加到列表中的 lti
  • 当您的构造函数超出范围时,lti 也会;并且该内存可以被应用程序的任何其他部分重用。
  • 稍后当您的计时器尝试更新时,FTimeInformation 中的引用仍然存在。
  • 您使用此引用来查找 lti 在哪里
  • 如果该部分内存已被应用程序的任何其他部分更改,则ltr-&gt;ListItem 不再引用在构造函数中创建的 TListItem。相反,它引用它尝试使用的内存的其他部分,就好像它是一个 TListItem。因此,您会遇到“奇怪”的问题,例如:
    • li->删除 == false
    • li->ClassName 导致访问冲突。

注意:您是否真的遇到访问违规通常取决于运气:如果您确实遇到访问违规,请考虑自己幸运;另一种选择通常是“莫名其妙”的不稳定行为。

尝试如下修改你的构造函数,它应该修复访问冲突。注意:lti 现在是动态分配的,你必须决定何时释放它,否则你会有内存泄漏;)

LOCATIONTIMEINFORMATION* lti = new LOCATIONTIMEINFORMATION;
lti->TimeZoneName = "UTC";
lti->PlaceName = "Near Greenwich, England";
lti->UtcOffsetMinutes = 0;
AddTimeInformation(*lti);

【讨论】:

  • 您说过“然后将 lti 添加到列表中;或者更准确地说:您将 lti 的引用添加到列表中。”但我不确定那是正确的。我在问题的末尾添加了一个代码示例(它不适合最多 600 个字符的评论)。代码示例似乎显示 list.push_back 添加了结构的 copy,而不是引用。你能运行一下看看吗?
  • -10 表示诊断不正确。 push_back() 在(至少)一个 STL 列表中,添加一个 copy,而不是结构的 reference。只有在访问 STL 列表项时,它才会返回引用。
  • 你是对的;我的回答不正确。事实上,我已经将您的原始代码插入到 CPPB 2009 中,将框架放在表单上,​​并且效果很好。这表明您的错误不在您演示的代码中,而可能在您使用框架的方式上?
【解决方案2】:

我实际上看不出代码有任何问题。

尝试从您的迭代器中打印 TimeZoneName 或 PlaceName,而不是 li->ClassName(),以确保您没有不小心将其他内容添加到列表或其他内容中...

【讨论】:

  • 做ShowMessage(itr->PlaceName);作品。这意味着 itr->ListItem 在某处“毁了”?
  • 所以,问题实际上是我的错。永远不要在 ctor 中添加 TListItem 等。
【解决方案3】:

您对 FTimeInformation 有什么价值观?例如,li == NULL 吗?


如果我们可以假设访问冲突在循环中第一次发生,并且 li 指向一个有效的 TListItem,那么也许我们应该将一行中的三个语句分成三行。像这样的:

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

如果访问冲突没有发生在第一行,这将告诉我们一些有趣的事情。

【讨论】:

  • 不走运。请参阅问题中的“更新:”。基本上,只有在计时器内调用 UpdateTimes() 时才会发生访问冲突。
  • 计时器是否使用单独的线程?如果是这样,那么对共享值的访问应该以线程安全的方式进行,否则不能保证在一个线程中所做的更改对另一个线程是可见的。
  • 没有单独的线程..请在我的问题中查看新的第二个“更新:”
  • 只是为了在您的第三次更新中扩展解决方案,lti 是在构造函数范围内的堆栈上创建的,因此当构造函数返回时它会超出范围。 lti 按值传递到 AddTimeInformation(...),它创建通过 push_back(const T&) 引用添加到列表的副本。在 AddTimeInformation(...) 结束时,副本超出范围,在列表中留下对回收内存的引用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多