2

我的问题有点复杂,但我试图简单地描述它。我想在我的一个类中使用本地静态成员(类型std::map<std::string, std::weak_ptr>)实现一个静态方法。每次调用此方法时,它都应该查找映射中是否存在以传递的参数为键的对象。该方法返回的值是一个std::shared_ptr(从std::weak_ptr地图中的 构造,如果std::weak_ptr可以锁定 - 否则std::shared_ptr构造一个新的并添加std::weak_ptrstd::map)。但是我在拨打电话的线路上“有时”会收到访问冲突std::map.find()

“有时”的意思是:如果一个std::weak_ptr被添加到地图中,然后因为它无法被锁定而被删除 - 并且一个新std::shared_ptr的被构建,添加std::weak_ptrstd::map. 下次我的静态方法尝试在std::map其中查找时,可能(偶尔)来自以下位置的访问冲突:

File: Microsoft Visual Studio 11.0\VC\include\xtree
Line: 2092
Method: _Nodeptr _Lbound(const key_type& _Keyval)
Access Violation at reading: '_Nodeptr _Pnode = _Root();'

我找不到任何方法来更好地调试问题 - 非常感谢这里的任何帮助。

最后但并非最不重要的一点是,我重写了一些代码,以获得一个简短的、不言自明的示例。但到目前为止,我无法在这里重现访问冲突。

#include <map>
#include <memory>
#include <string>
#include <iostream>

class MyClass{
public:
  MyClass(int a){
    this->a = a;
  }
  virtual ~MyClass(){ }
private:
int a;
};

class MyStaticClass{
public:
  static std::shared_ptr<MyClass> myMethod(const char* string){
    static std::map<std::string, std::weak_ptr<MyClass>> map;
    std::shared_ptr<MyClass> retVal = nullptr;
    std::map<std::string, std::weak_ptr<MyClass>>::iterator iter = map.find(std::string(string));

    if(iter != map.end()){
      retVal = iter->second.lock();
      if(!retVal){
        /* ptr is gone already, so remove it from map */
        iter = map.erase(iter);
      }
    }
    if(!retVal){
      /* not found in map OR erased - need to be created again */
      retVal = std::make_shared<MyClass>(atoi(string));
      std::weak_ptr<MyClass> weakRetVal = retVal;
      map.insert(std::make_pair(std::string(string), weakRetVal));
    }
    return std::move(retVal);
  }
};

int main(int argc, char* argv[]){
  {
    std::shared_ptr<MyClass> myIntPointer = MyStaticClass::myMethod("1");
  }
  {
    std::shared_ptr<MyClass> myIntPointer = MyStaticClass::myMethod("1");
  }
  {
    std::shared_ptr<MyClass> myIntPointer = MyStaticClass::myMethod("1");
  }
  return 0;
}

编译器/平台:VS 2012 / Windows 8

编辑:到目前为止,我发现,当这个错误发生时,'map' 的大小总是再次为 0(至少根据调试器)。因此,即我以未初始化的地图启动程序(当然大小为0)。然后使用 myMethod() 添加条目 - 映射的大小为 4。现在 std::weak_ptr 到期,我再次调用 myMethod()。调试器现在显示 map.size() 将再次为 0(永远不会删除映射条目,因此这应该是不可能的)。

Edit2:当大小应该是 0x00000004 时,调试器也会显示 0xff000004 作为大小(当然大多数“条目”都不能显示)。本地静态存储是否存在任何 32 位/64 位问题?

4

1 回答 1

2

我找到了解决问题的方法,就像来自另一段代码的堆栈/堆损坏中的注释所建议的那样。以防万一有类似问题的人有一天会发现这个问题,我将简要描述到底出了什么问题以及我找出原因的步骤。

1.)因为我在使用普通全局函数和全局静态 std::map 时没有遇到这个问题,这表明在类似的存储范围(局部函数静态)中存在问题。使用全局静态 std::map 时没有发生访问冲突的原因很简单,就是我的堆栈/堆损坏发生在不同类中的不同本地函数静态对象上。因此,对同一存储范围内的对象的副作用比对不同存储范围的副作用(即局部函数静态VS全局静态)更有可能。

2.) 我刚刚执行了我的代码很多次,以在发生访问冲突时识别出一种模式,以及我的步骤是什么来重现该行为。然后我注意到,如果我按下键盘上的某个键,我的访问违规就会越来越频繁地发生。这对我来说似乎已经是一个非常奇怪的副作用,让我特别检查我的输入类。

3.) 在那里我找到了致命的一段代码:我对我的一个输入类使用了单例方法(这意味着:该类有一个静态 getInstance() 方法,该方法返回该类本身的一个本地函数静态对象)。提醒一下:我的访问冲突也发生在本地函数静态对象上。非常可疑......通过这个类进行更深入的研究,我找到了一个将实际键值存储在数组中的成员。数组大小是使用一些晦涩的宏声明的,这些宏简化为:

unsigned char keyValues[sizeof(unsigned short)];

但当然,sizeof()运算符不会返回类型的最大值 - 而是以字节为单位的大小(即 x86 和 x64 上的 2 - 或者在某些情况下因为对齐而返回 4)。因此,如果我将 sizeof(unsigned short) 替换为 65536 (或USHRT_MAXfrom <climits>),我的代码会在没有任何访问冲突的情况下正常运行... :( 愚蠢的错误会带来致命的后果...

于 2013-10-22T10:52:55.680 回答