对于具有相同调用堆栈的同一对象,从同一线程两次调用该函数

时间:2019-05-08 14:38:50

标签: c++ visual-studio debugging

背景

我正在调试一个我愿意加入并学习C ++的开源项目。然后,我尝试扩展其中一个问题所需的某些功能,然后偶然发现了一个有趣的案例(我一生中从未见过这样的事情)。

源代码

QuestSet Player::GetQuestForEvent(uint16 eventId) const
{
    QuestSet eventQuests; // QuestSet is typedef for std::set<uint32>

    for (uint8 i = 0; i < MAX_QUEST_LOG_SIZE; ++i)
    {
        uint32 questId = GetQuestSlotQuestId(i);
        if (questId == 0)
            continue;

        QuestStatusMap::const_iterator qs_itr = m_QuestStatus.find(questId);
        if (qs_itr == m_QuestStatus.end())
            continue;

        QuestStatusData const& qs = qs_itr->second;

        Quest const* qinfo = sObjectMgr->GetQuestTemplate(questId);
        if (!qinfo)
            continue;

        if (qinfo->GetEventIdForQuest() == eventId)
            eventQuests.insert(questId);
    }

    return eventQuests; <--- breakpoint here
}

我注意到我的服务器因

崩溃
  

worldserver.exe中0x00007FFE1BE6A388(KernelBase.dll)的未处理异常:0xC0000005:访问冲突。

此日志来自我的服务器

  

C:\ Program Files(x86)\ Microsoft Visual Studio \ 2019 \ Community \ VC \ Tools \ MSVC \ 14.20.27508 \ include \ xtree(240):断言失败:范围内的映射/设置迭代器来自不同的容器

因此,我在上述位置放置了断点,并注意到该函数被调用了两次。我知道上载图片可能不是最好的选择,但在我看来,这是最好的解释,所以请看一下。

屏幕截图

  • 第一个断点命中 first breakpoint hit

  • 第二个断点命中(诊断工具窗口中的“通知事件”窗口) second breakpoint hit

这是项目中唯一调用此函数的地方。

void OnLogin(Player* player, bool /*firstLogin*/) override
{
    QuestSet eventQuests = player->GetQuestForEvent(1);
    /*std::for_each(eventQuests.begin(), eventQuests.end(), [&player](uint32 questId)
    {
        player->AbandonQuest(questId);
    });*/
}

我知道这听起来很愚蠢,但是我没有建议,请您指出正确的方向,理解为什么这个断点最有可能被击中两次的原因也会解决该异常。

故障排除:

  • 项目是在Debug配置中构建的
  • 在两次点击中,
  • 调用堆栈,线程和this对象都是相同的。

1 个答案:

答案 0 :(得分:2)

该函数很可能实际上并未两次调用。您只是因为两次击中相同的断点而得出了错误的结论。

Visual Studio允许您检查断点附近的汇编代码,我相信默认值为Ctrl+Alt+D。在该处,您将看到混合的汇编指令(您的CPU实际执行的操作)和“以下指令的源代码行”的调试信息。

我希望您会看到return eventQuests;行与两个汇编指令块相关联,在这两个指令块之间。您将首先在第一组指令中命中断点,然后执行第二组指令(例如与循环或右括号相关),然后由于第二组而再次命中断点。是的,听起来很傻,但是MSVC输出的调试信息并不是我所见过的最好的。

另一种简单的检查方法是在返回之前插入打印语句(例如std::cout << "Hi" << std::endl),并检查是否获得一或两个打印输出。除非得到两次打印输出,否则得出结论,实际上多次输入该函数是错误的。