this指针在调用成员函数时改变

this-Pointer changing on call of member function

本文关键字:函数 改变 成员 调用 指针 this      更新时间:2023-10-16

首先,我要为这个问题的不清晰表达提前道歉——如果我知道该问什么,我可能就知道如何解决它了。…但是我甚至没有一个模糊的理论,尽管有七年的c++经验。如有任何有用的指示(hè),将不胜感激。

  • 可能与这个问题有关。(症状)
  • 与那个无关。(这里没有明确的函数指针)

问题:具有某些属性的对象的计数,这些对象本身由其类的成员函数检查,结果不正确。问题的根源在于检查是用乱码值进行的。原因在于指针"this"在调用函数和进入函数体之间发生了变化。离开主体后,指针又正确了。

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

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

  • 我能做什么?作为权宜之计,用函数体的副本替换函数调用是有效的,但这不是正确的解决方案。

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

简明:我可以遵循哪些步骤来更深入地了解问题的本质?

已处理事项的简短清单:

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

和一个可能相关也可能不相关的提示,但我现在无法解释:

  • 对于第一个十六进制,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;
}

额外的输出(以及dump的初始化和关闭)来自控制流的下一个更高级别,另一个类中的另一个函数,其中循环遍历所有六边形。我现在省略了这一点,但如果有人认为这很重要,我会添加的。

和相应的。现在在我看来,这个错误似乎是由工具中的错误造成的,而不是代码中的错误。事实上,即使函数现在计算正确,调试器仍然显示了之前的错误指针及其无意义成员。

EDIT for OP EDIT:

现在闻起来更像是违反了ODR。更改内联函数并使其更改程序行为,正是由于违反ODR而导致的未定义行为可能发生的情况。你在任何地方都使用模板吗?此外,尝试在原始版本中取消内联IsSea(),看看是否有帮助。

(原始回答):对我来说,这味道像三样东西中的一种。

首先,它可能违反了所讨论的函数的单定义规则。一定要确保在不同的翻译单元中没有多个版本,或者在不同的单元中没有不同的编译设置。

其次,编译器可能会因为使用保留名称_Area而做一些事情。无论如何,你都应该解决这个问题。

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

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

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

VS2015的调试器为this指针显示的值-扩展为指向的对象的所有成员-确实是不正确的,并且以一种非常可复制的方式:

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

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

最初促使我打开这个主题的错误,它持续存在于不同构建设置的几个编译中-在我输入fprinf命令将指针地址转储到文件以发现上面描述的错误后,它已经消失并且不能再重现。尽管代码与之前的错误代码逐字相同,但现在它可以完美地工作。进一步的改变并没有改变这一点。不管我怎么努力,我都无法挽回。

这一切都是侥幸。 。这意味着它随时可能再次发生,没有明显的原因,没有任何预防措施。很好,不是吗?

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