【问题标题】:Pointer mysteriously resetting to NULL指针神秘地重置为 NULL
【发布时间】:2009-03-02 00:31:20
【问题描述】:

我正在开发一款游戏,目前正在开发处理输入的部分。这里涉及三个类,ProjectInstance 类启动关卡和东西,GameController 将处理输入,PlayerEntity 将受GameController 确定的控件影响。在启动关卡时,ProjectInstance 会创建GameController,它会在游戏循环中调用的 Step 方法中调用其EvaluateControls 方法。 EvaluateControls 方法看起来有点像这样:

void CGameController::EvaluateControls(CInputBindings *pib) {
    // if no player yet
    if (gc_ppePlayer == NULL) {
        // create it
        Handle<CPlayerEntityProperties> hep = memNew(CPlayerEntityProperties);
        gc_ppePlayer = (CPlayerEntity *)hep->SpawnEntity();
        memDelete((CPlayerEntityProperties *)hep);
        ASSERT(gc_ppePlayer != NULL);
        return;
    }

    // handles controls here
}

这个函数被正确调用并且断言永远不会触发。但是,每次调用此函数时,gc_ppePlayer 都会设置为 NULL。如您所见,它不是超出范围的局部变量。 gc_ppePlayer 可以设置为 NULL 的唯一位置是在构造函数中或可能在析构函数中,在调用 EvaluateControls 之间都不会调用它们。调试时,gc_ppePlayer 在返回之前收到正确的预期值。当我再次按 F10 并且光标位于右括号时,该值变为 0xffffffff。我在这里不知所措,怎么会发生这种情况?有人吗?

【问题讨论】:

    标签: c++ pointers


    【解决方案1】:

    gc_ppePlayer == NULL 上设置一个观察点,当该表达式的值发生更改(变为 NULL 或从 NULL)时,调试器将指向您确切发生的位置。

    试试看会发生什么。寻找未终止的字符串或将 mempcy 复制到太小的内存等...通常这是导致全局/堆栈变量被随机覆盖问题的原因。

    在 VS2005 中添加观察点(brone 的说明)

    1. 转到断点窗口
    2. 点击新建,
    3. 单击数据断点。输入
    4. &amp;gc_ppePlayer 在地址框中,离开 仅其他值。
    5. 然后运行。

    gc_ppePlayer 改变时, 断点 会被击中。 – 兄弟

    【讨论】:

    • 嗯,我在哪里做呢?我有一个 Watch 对话框,但我认为这不是你的意思。我正在使用 Visual Studio 2005。
    • 我没有 VS 方面的经验,但任何值得一提的调试器都应该能够设置观察点(本质上是在值更改时触发的数据断点)。您的下一个问题可能是如何在 VS2005 中设置观察点;)
    • @hhafez - 我不知道何时可以检测到 VS2005 中的值何时发生变化。我以为您只需在各个关键位置设置断点,然后查看该值是否已更改为所需的值(您也可以通过条件断点来实现)。
    • 在 Visual Studio 中,您将通过设置条件断点来执行此操作。见support.microsoft.com/kb/308469
    • 在 VS 中右键单击右侧排水沟。将出现设置条件断点的选项。
    【解决方案2】:

    您是在调试 Release 还是 Debug 配置?在发布构建配置中,您在调试器中看到的并不总是正确的。进行了优化,这可以使监视窗口显示您所看到的古怪值。

    您真的看到 ASSERT 触发了吗? ASSERT 通常是从 Release 构建中编译出来的,所以我猜您正在调试发布构建,这就是 ASSERT 不会导致应用程序终止的原因。

    我建议构建一个 Debug 版本的软件,然后查看 gc_ppePlayer 是否真的为 NULL。如果确实如此,那么您可能会看到某种类型的内存堆损坏,其中该指针被覆盖。但如果是内存损坏,它的确定性通常比您描述的要低得多。

    顺便说一句,使用像这样的全局指针值通常被认为是不好的做法。如果它确实是单个对象并且需要全局访问,请查看是否可以将其替换为单例类。

    【讨论】:

    • +1 对于第一部分,这正是我要说的,但是单例与全局数据一样糟糕(我书中的反模式)一个简单的实例化和引用/指针更多模块化。
    • @Robert - 我认为单例在被滥用时可能是反模式,这通常是这种情况。如果一个对象在系统中确实是一个 SINGLE 对象,并且在法律上永远不会超过 1 个,那么单例模式不是最适合使用的吗?比原始指针/引用更好..
    • ..恕我直言,因为至少您可以跟踪对它的访问。
    • 是的,这确实是问题所在。我使用 Release 是因为 Debug 无法启动,但是当我(最终)解决了这个问题时,事实证明你是对的。 gc_ppePlayer 根本没有有效值。现在找出原因:-p
    【解决方案3】:

    我的第一个想法是说 SpawnEntity() 正在返回一个指向内部成员的指针,该内部成员在调用 memDelete() 时正在“清除”。我不清楚指针何时设置为 0xffffffff,但如果它发生在调用 memDelete() 期间,那么这就解释了为什么您的 ASSERT 没有触发 - 0xffffffff 与 NULL 不同。

    您有多久没有重新构建整个代码库了?我经常看到这样的内存问题,只需重建整个解决方案即可解决。

    您是否尝试过在函数结束时单步执行 (F11) 而不是跨步 (F10)?尽管您的示例没有显示任何局部变量,但为了简单起见,您可能遗漏了一些变量。如果是这样,F11 将(希望)进入任何这些变量的析构函数,让您查看其中一个是否导致问题。

    【讨论】:

    • 这个函数中唯一的变量在返回值之下,所以它们的析构函数不会被调用。无论如何,我正在使用的 SDK 中的 cmets 说 CreateEntity 将实体与其属性联系起来(用于在编辑器内部使用),而 SpawnEntity 是分隔符,用于在播放时创建实体。
    • 如果您暂时移除对 memDelete() 的调用会怎样?指针是否完好无损?
    【解决方案4】:

    你有一个“核心上的 fandango”。

    动态初始化正在覆盖内存的各种位(原文如此)。

    全局被直接或间接覆盖。 内存中的全局相对于堆在哪里?

    二进制切掉动态初始化的部分,直到问题消失。 (一次注释掉一半,递归)

    【讨论】:

      【解决方案5】:

      根据您使用的平台,有一些工具(免费或付费)可以快速找出此类内存问题。

      在我的头顶:

      【讨论】: