【问题标题】:Mis-indexed char[] arrays in Visual Studio 2005Visual Studio 2005 中索引错误的 char[] 数组
【发布时间】:2010-10-12 17:42:21
【问题描述】:

我无法解释我继承的 C++ 类的 char[] 数组中似乎有什么错误寻址。我正在使用 Visual Studio 2005 并将问题范围缩小到:

MyClass.h:

class MyClass {
public:
  MyClass();
  virtual ~MyClass();
  void Reset();
  // More member functions. . .

  char m_szString[128];
  // More member variables. . .
}

MyClass.cpp:

MyClass::MyClass() { Reset(); }
MyClass::Reset()   { m_szString[0] = 'X'; }
// . . .

当我单步执行程序时,我发现Reset() 函数实际上将m_szString[4] 设置为'X'——而不是m_szString[ 0] 正如我所料。根据监视窗口,m_szString[] 之前的类中唯一的元素是指向 vftable 的指针,__vfptr,恰好是 4 个字节。

如果我向MyClass 添加更多成员变量,则后续字符串会被各种错误寻址,并且总是增加4 个字节的倍数。不仅仅是 aligned 到 4 字节边界,而是实际上 offset 为 4 的倍数。就好像编译器为每个 vftable 跳过了两倍的必要空间......但那是纯属猜测。

报告了一些类似的问题(Google、MSDN),但我没有找到任何答案。


其他信息/部分解决方案:该类是成为 DLL 的包装类的唯一成员变量。父级最初声明为

class ATL_NO_VTABLE CWrapperClass

删除ATL_NO_VTABLE 修复了对齐问题。

我仍然看到缓冲区溢出,但应该很容易找到。

您能否解释一下ATL_NO_VTABLE 对于嵌入式 C 开发人员的解释,他们在 BSTR 之外的 COM 经验非常有限,或者更好的是,提供一个指向良好参考的指针(抱歉)?


还有更多:This question 提供了一些有用的调试信息。

感谢您的帮助。

【问题讨论】:

  • 您可以使用打印语句并在发布模式下尝试一下吗?
  • 你能给出一个完整的工作示例来演示这个问题吗?
  • 这里肯定比你提到的要多 - 例如,关于“调整器 thunks”的评论让我感到怀疑。
  • @Andrew:正如我现在所知道的那样。我仍在研究代码,对 COM 更加困惑。感谢您继续挖掘的动力!

标签: arrays visual-c++ visual-studio-2005 pointers


【解决方案1】:

答案

事实上,编译器为某些类成员计算了错误的地址。罪魁祸首原来是隐藏在一个不起眼的头文件中的 #pragma pack 1 指令,该头文件在某些​​源文件中是 #included

我是怎么找到的

50% 的坚持、50% 的运气和 10% 的数学技能。 :-)

我遇到了一个非常简单的类,它的方法和成员很少,大部分都是在头文件中定义的,包括:

typedef int BOOL;  // Legacy, makes my skin crawl; don't ask.
BOOL isConnected() { return m_bConnected; };
BOOL m_bConnected;

...当我知道 m_bConnectedtrue 时,函数返回false

  • 监视窗口显示正确的值。对m_bConnected 的程序化更改已反映在窗口中。
  • &m_bConnected 的观察显示它从类的 8 个字节开始。
  • 原始内存窗口显示正确的值。对m_bConnected 的程序化更改也反映在此处。
  • m_bConnected之前的4个字节都是0,我解释为填充。
  • 通过代码单步调试调试器清楚地显示返回值false

于是我查看了反汇编窗口,发现了这个(cmets 是我的):

mov    eax,dword ptr [this]        ; address of the class instance
mov    eax,dword ptr [eax+4]       ; offset to m_bConnected

换句话说,编译器将偏移量计算为 4,而它应该是 8。

啊哈!

有趣的是,当我从标题中删除isConnected() 的定义并将其放入源文件时,地址计算正确!这让我确信这确实是一个打包问题,在代码库中搜索#pragma pack 很快就发现了罪魁祸首,一个古老的头文件(合法地)需要在 1 字节边界上对齐,但从未将打包重置为默认。

修复

解决方案很简单,就像这样包含有问题的标题:

#pragma pack(push)
// original code here
#pragma pack(pop)

感谢您的关注。还有 Sara,如果你正在阅读,我马上要dance on my desk

【讨论】:

    【解决方案2】:

    在开始使用 .通过这样做,即使在调试时,您也将拥有更清晰的阵列视图。 更何况你已经在 public 中声明了 m_szString 。最好在私有区域声明数组并使用成员函数返回它

    【讨论】:

    • 是的,但与问题无关。无论如何声明或初始化任何元素,索引都应该始终正确。这不是关于最佳实践的问题;这是关于编译器计算地址方式的问题。
    【解决方案3】:

    我的第一个猜测是编译时开启了意想不到的优化。

    我的第二个猜测是 unicode 支持会发生一些奇怪的事情(例如 char 与 wchar_t)。

    【讨论】:

    • 感谢您的意见。问题肯定是 ATL_NO_VTABLE,可能与 COM 有关。将一串字符写入数组实际上确实将正确的字符写入连续元素;这只是错误的偏移量。我已经对所有优化进行了四次检查——它们都关闭了。
    【解决方案4】:

    这里有两个问题:

    • 有什么问题?我的意思不是在调试器中看起来很可疑,而是在顶层实际上出了什么问题?您在调试器中看到的可能是优化的结果,而不是实际错误。
    • 您是否有一些重现该错误的最小代码?以上并没有告诉我太多。如果我之前遇到完全同样的问题,它可能会有用,但我没有。那么如果我想重现这个问题,我应该怎么做呢?

    【讨论】:

    • 感谢您的回复!第一个症状是调试器报告缓冲区溢出,因为它从使用此类的函数返回。进一步检查显示 char[] 数组没有按预期设置,这最终导致了 'off by 4' 观察。所有优化均已禁用。
    猜你喜欢
    • 2011-06-21
    • 2023-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-28
    • 1970-01-01
    • 2011-08-26
    相关资源
    最近更新 更多