this-Pointer在成员函数调用时更改

时间:2015-10-01 22:37:01

标签: c++ visual-studio-2015

一开始,就问题的模糊描述提前道歉 - 如果我知道要求什么,我可能会知道如何解决它。 ...但我甚至没有一个微弱的理论,尽管有七年的C ++经验。任何有用的指针(hè)将非常受欢迎。

  • 可能与this question有关。 (相同的症状)
  • that one无关。 (这里没有明确的函数指针)

问题:具有某些属性的对象计数,这些属性本身由其类的成员函数检查,显示不正确的结果。源头是检查发生在胡言乱语的价值观。其来源是指针“this”在调用函数和进入其体之间发生变化。离开主体后,指针再次正确。

可悲的是,相关问题的解决方案在这里不起作用。

我无法提出问题的最小例子。 此外,据我所知,在同一个程序中正确地调用了数百个成员函数,但没有表现出这种行为。

  • 我该怎么办? 作为一个bandaid,用它的正文副本替换函数调用,但这不是正确的解决方案。

我完全不知道如何进行诊断。

简明:我可以采取哪些步骤来深入了解问题的本质?

已经处理好的事项的简短清单:

  • 有问题的对象在通话时已正确初始化。
  • 所有优化都已关闭。没有内联。这是一个调试版本,其中包含有效的相应设置。
  • 清理和重建项目并没有产生不同的结果。
  • 在成功测试绑定解决方案后,使用原始(但重新输入的)函数调用重新编译导致问题的返回。
  • 所涉及的编译单元中没有编译器警告(警告级别3),特别是在项目范围内禁用的是:
    • C4005(宏重新定义,由于兼容性原因使用自定义/被黑客攻击的Windows SDK - 这最初是Win95程序)
    • C4244(隐式转换为较小的类型,由于遗留代码等待重构 - 这些都是缺少显式转换的float-to-int转换,所有800多个实例)
    • C4995(由于在没有前面的下划线的情况下调用C-lib函数,因此不推荐使用#pragma标记的调用函数 - 希望最终切换到GCC)
  • “控制流量保护”和“基本运行时检查”已启用,但不会触发。

暗示可能相关或不相关,但我现在无法解释:

  • 对于第一个十六进制,IsSea被正常调用,即:“this”内部与“this”外部相同
  • 只有在后面的所有咒语中才会发生错误。
    • 改变后的“this”指针虽然没有指向第一个十六进制,但它似乎打不到未分配的空间。

以下是它的外观摘录:

部首:

// These three are actually included from other files.
constexpr unsigned int AREA_none = 0u;
constexpr unsigned int AREA_Lake = 1u;
enum Terrain
{
  OCEAN = 8,
  TERRA_INCOGNITA = 10
};

class CHex
{
public:
  CHex(); // initialises ALL members with defined error values
  bool ReadHex(); // Init-type function calling the problematic one
  bool IsSea() const // problematic function
  { return this->_Area != AREA_none && this->_Area != AREA_LAKE && this->_nTerrain == Terrain::OCEAN; }
  // The body does the right thing - just WITH the wrong thing.
protected:
  unsigned int _Area;
  int _nNavalIndex;
  Terrain _nTerrain;

  static int _nNavalCount = 0;

  // There are a lot more functions in here, both public and protected.
  // The class also inherits a bunch from three other classes, but no virtual functions and no overlaps are involved.
}

来源:

CHex::CHex() : _Area{0u}, _nNavalIndex{0}, _nTerrain{Terrain::TERRA_INCOGNITA}
{}
bool CHex::ReadHex()
{
  // Calls a lexer/parser pair to procure values from several files.
  // _Area and _nTerrain are being initialised in this process.
  // All functions called here work as expected and produce data matching the source files.

  // _Area and _nTerrain have the correct values seen in the source files at this point.
  if(this->IsSea()) // but inside that it looks as if they were uninitialised
    // This ALWAYS happens because the function always returns true.
    _nNavalIndex = _nNavalCount++;
  // Stopping at the next instruction, all values are again correct
  // - with the notable exception of the two modified by the instruction that should not have happened.

  // If I replace that with the following, I receive the correct result:
  /*
  // direct copy of the function's body
  if(this->_Area != AREA_none && this->_Area != AREA_Lake && this->_nTerrain == Terrain::OCEAN)
    _nNavalIndex = _nNavalCount++; // only happens when it should; at the end, the count is correct
  */

  // Sanity checks follow here.
  // They too work correctly and produce results appropriate for the data.

  return true; // earlier returns exist in the commented-out parts
}

对于这个大混乱再次抱歉,但好吧,现在一团糟。这就像看到物理学的基本定律发生了变化。

-

根据@Ben Voigt的建议,我入侵了一个将指针转储到文件中的诊断程序。看哪:

Before ReadHex: 20A30050 (direct array access) On ReadHex:  20A30050    On IsSea:   20A30050 (with members: 0, 8) After ReadHex:    20A30050 
Before ReadHex: 20A33EAC (direct array access) On ReadHex:  20A33EAC    On IsSea:   20A33EAC (with members: 2, 0) After ReadHex:    20A33EAC 
Before ReadHex: 20A37D08 (direct array access) On ReadHex:  20A37D08    On IsSea:   20A37D08 (with members: 2, 0) After ReadHex:    20A37D08 
Before ReadHex: 20A3BB64 (direct array access) On ReadHex:  20A3BB64    On IsSea:   20A3BB64 (with members: 3, 0) After ReadHex:    20A3BB64 
Before ReadHex: 20A3F9C0 (direct array access) On ReadHex:  20A3F9C0    On IsSea:   20A3F9C0 (with members: 4, 3) After ReadHex:    20A3F9C0 
Before ReadHex: 20A4381C (direct array access) On ReadHex:  20A4381C    On IsSea:   20A4381C (with members: 3, 0) After ReadHex:    20A4381C 
[...]

他们都是正确的。他们中的每一个。 甚至更好:该功能现在正确评估 这是更改的来源(我这次省略了评论):

部首:

// These three are actually included from other files.
constexpr unsigned int AREA_none = 0u;
constexpr unsigned int AREA_Lake = 1u;
enum Terrain
{
  OCEAN = 8,
  TERRA_INCOGNITA = 10
};

extern FILE * dump;
class CHex
{
public:
  CHex();
  bool ReadHex();
  bool IsSea() const {
    fprintf(dump, "\tOn IsSea:\t%p (with members: %u, %i) ", (void*)this, this->_Area, this->_nTerrain);
    return this->_Area != AREA_none && this->_Area != AREA_LAKE && this->_nTerrain == Terrain::OCEAN; }
protected:
  unsigned int _Area;
  int _nNavalIndex;
  Terrain _nTerrain;

  static int _nNavalCount = 0;

  // lots more functions and attributes
}

来源:

CHex::CHex() : _Area{0u}, _nNavalIndex{0}, _nTerrain{Terrain::TERRA_INCOGNITA}
{}
bool CHex::ReadHex()
{
  fprintf(dump, "On ReadHex:\t%p ", (void*)this);

  // Calls a lexer/parser pair to procure values from several files.
  // _Area and _nTerrain are being initialised in this process.

  if(this->IsSea()) // Suddenly works!?
    _nNavalIndex = _nNavalCount++;

  // Sanity checks follow here.

  fprintf(dump, "After ReadHex:\t%p ", (void*)this);
  return true;
}

附加输出(以及转储的初始化和关闭)来自控制流中的下一个更高级别,另一个类中的另一个函数,其中所有六边形的循环驻留。我暂时省略了,但如果有人认为这很重要,我会加上它。

顺便说一下。它现在看起来好像这个错误是工具中的错误导致的,而不是代码中的错误。事实上,即使函数现在正确评估,调试器仍然显示来自之前的错误指针及其无意义的成员。

2 个答案:

答案 0 :(得分:1)

编辑OP编辑:

这现在闻起来甚至更多,就像ODR违规一样。更改内联函数,并具有该更改程序行为正是由于ODR违规引起的未定义行为可能发生的情况。你在任何地方使用模板吗?另外,请尝试在原始版本中取消内联IsSea()以查看是否有帮助。

(原始答案): 这对我来说就像三件事之一。

首先,它可能是相关函数的单定义规则违规。绝对确定不同翻译单元中没有多个版本,或者不同单元中的编译设置不同。

其次,由于您使用保留名称_Area,编译器可能正在执行某些操作。无论如何,你都应该解决这个问题。

第三,VC ++可以使用不同的成员函数指针机制,其中一个可能会影响你的情况(即使你没有显示使用成员函数指针)。有关一些信息,请参阅https://msdn.microsoft.com/en-us/library/83cch5a6.aspx?f=255&MSPPError=-2147217396。另一种可能性是这些指针的编译器选项在翻译单元中是不同的。

答案 1 :(得分:1)

这是一个非常悲伤的答案,但唉,有时一个悲伤的答案仍然是正确的。

让我们从较小但至少有些有用的部分开始:

值VS2015的调试器显示为this指针 - 并且在扩展中指向的对象的所有成员 - 确实是错误的并且以非常可重复的方式显示:

如果在头文件中定义的成员函数上设置了断点,"这个"显示在调试器中将显示 - 此函数的入口点。它仍将具有所讨论的对象的类型并显示所有成员......但是当这些值被填充为来自所述函数的入口点的偏移时,它们的显示内容当然是无意义的。 所有这些纯粹是一个UI问题,不会影响实际的程序执行。

现在是无用而令人沮丧的部分:

最初促使我打开这个主题的错误,通过几个具有不同构建设置的编译持续存在 - 已经消失,在我输入fprinf命令将指针地址转储到文件后再也无法再现发现上面描述的错误。 即使代码是一字母一致的,与之前有缺陷的代码完全相同,但它现在可以完美运行。进一步的改动没有改变这一点。我不能把它带回去,尽我所能。

这整个卑鄙的事情 - 是一个侥幸。 不知何故。这意味着它可能在任何时候再次发生,没有明显的理由,没有任何预防手段。好的,不是吗?

...

无论如何,衷心感谢@Ben Voigt提出的可能与调试器输出相关的概念可能与现实无关。 同样,感谢@dyp指出并解释了一个不相关的潜在问题(名称前缀为' _'后跟大写字母为保留表达式)我以前没有意识到。 感谢@ Mark-B实际提供关于问题的假设,即使它们都没有被证明是正确的。至少有一些,也可能有一个。