【问题标题】:C# & C++: "Attempted to read or write protected memory" ErrorC# & C++:“试图读取或写入受保护的内存”错误
【发布时间】:2009-11-29 13:01:36
【问题描述】:

以下代码编译没有错误。基本上,C#2005 控制台应用程序调用 VC++2005 类库,后者又调用本机 VC++6 代码。运行 C#2005 应用程序时出现以下错误:

未处理的异常:System.AccessViolationException:试图读取或写入受保护的内存。这通常表明其他内存已损坏。

这个错误的原因是什么?以及如何去纠正它?

Edit1:它在 StdStringWrapper ssw = w.GetNext();

行崩溃

Edit2:我听从了 Naveen 的建议,使用整数索引而不是迭代器,现在没有更多错误了。非常感谢所有发表评论的人!

用 C#2005 编写的代码作为控制台应用程序:

class Program
{
    static void Main(string[] args)
    {
        Class1 test= new Class1();
        test.PerformAction();
        test.PerformAction();
        test.PerformAction();
        test.PerformAction();
    }
}

在 VC++2005 中作为类库编写的代码:

public ref class Class1
{
    public:
        void PerformAction();       
};

void Class1::PerformAction()
{
    DoSomethingClass d;
    StdStringContainer w;
    d.PerformAction(w);

    for(int i=0; i<w.GetSize(); i++)
    {
        StdStringWrapper ssw = w.GetNext();
        std::cout << ssw.CStr() << std::endl;
    }
}

在 VC++6 中作为动态链接库编写的代码:

#ifdef NATIVECODE_EXPORTS
    #define NATIVECODE_API __declspec(dllexport)
#else
    #define NATIVECODE_API __declspec(dllimport)
#endif

class NATIVECODE_API StdStringWrapper
{
    private:
        std::string _s;

    public:     
        StdStringWrapper();
        StdStringWrapper(const char *s);
        void Append(const char *s);
        const char* CStr() const;       
};

StdStringWrapper::StdStringWrapper()
{
}

StdStringWrapper::StdStringWrapper(const char *s)
{
    _s.append(s);
}

void StdStringWrapper::Append(const char *s)
{
    _s.append(s);
}

const char* StdStringWrapper::CStr() const
{
    return _s.c_str();
}

//

class NATIVECODE_API StdStringContainer
{
    private:
        std::vector<StdStringWrapper> _items;
        std::vector<StdStringWrapper>::iterator _it;

    public: 
        void Add(const StdStringWrapper& item);
        int GetSize() const;        
        StdStringWrapper& GetNext();  
};

void StdStringContainer::Add(const StdStringWrapper &item)
{
    _items.insert(_items.end(),item);
}

int StdStringContainer::GetSize() const
{
    return _items.size();
}

StdStringWrapper& StdStringContainer::GetNext()
{
    std::vector<StdStringWrapper>::iterator it = _it;
    _it++;

    return *it;
}

//

class NATIVECODE_API DoSomethingClass
{
    public:
        void PerformAction(StdStringContainer &s);
};

void DoSomethingClass::PerformAction(StdStringContainer &s)
{
    StdStringWrapper w1;
    w1.Append("This is string one");
    s.Add(w1);

    StdStringWrapper w2;
    w2.Append("This is string two");
    s.Add(w2);
}

【问题讨论】:

  • 它在 StdStringContainer::GetNext() 中崩溃了吗?
  • 是的,我检查了它,它在 StdStringContainer::GetNext() 处崩溃。知道为什么吗?

标签: c# c++ visual-c++


【解决方案1】:

StdStringContainer 中的成员 _it 从未初始化为指向 _items 向量。这意味着它是一个无效的迭代器。当您在GetNext() 中将_it 分配给it 时,您已经给it 赋予了_it 中存在的无效、未初始化的值。然后,您通过_it++ 增加未初始化的_it,这就是触发您的错误的原因。

正如 Stroustrup 在 19.2 中所说,未初始化的迭代器是无效的迭代器。这意味着您未初始化的 _it 是无效的,并且使用它执行的操作是未定义的,并且可能会导致严重的失败。

但是,您的问题更深层次。迭代器的生命周期与其枚举的容器有着根本不同的生命周期。除非容器是不可变的并且在构造函数中初始化,否则实际上没有任何“好”的方法可以用这样的单个迭代器成员来做你想做的事情。

如果您不能公开 std:: 命名空间名称,您是否考虑过通过 typedef 给它们起别名,例如?您的组织或项目导致无法公开模板类的原因是什么?

【讨论】:

    【解决方案2】:

    在我看来,主要问题是您在 stdStringContainer 类中将 iterator 存储到 vector 中。请记住,每当向量调整大小时,所有现有的迭代器都会失效。因此,每当您对向量进行插入操作时,它可能会调整大小并且您现有的迭代器变得无效。如果您尝试在GetNext() 中取消引用它,那么它将访问无效的内存位置。为了检查这是否真的是这种情况,请尝试将初始向量大小保留为某个相对较大的数字,以便不会发生调整大小。您可以使用reserve()方法保留大小,这种情况下保证向量的capacity()大于或等于保留值。

    【讨论】:

    • 我想出了 StdStringContainer 类,因为我需要使用 std::vector 并能够对其进行迭代。但是,我不能将 std::vector、std::string 等 STL 变量作为函数参数公开。我该如何纠正这个错误?
    • 当向量重新分配时迭代器不会失效——指针会失效。这就是使用迭代器而不是向量上的指针的目标。从向量中删除元素时,迭代器可能会失效。
    • @KK:为什么不存储整数索引而不是迭代器?
    • 区分重新分配(一个实现可以根据需要在内存中随机移动一个向量)和失效(当向量改变时发生)。 Sgi 的文档似乎没有做出同样的区分。具体来说,通过 Stroustrup (19.2):“迭代器可能无效......因为它 [未] 初始化,它指向一个已调整大小的容器,该容器已被销毁,或者它表示序列的结束。”没有关于迭代器失效的“重新分配”概念。
    • 可以用整数索引访问std::vector中的项目吗?
    【解决方案3】:

    听起来你有内存泄漏。我建议查看有指针算术、写入内存或数组使用的任何地方。检查数组访问中的边界条件。

    另一个问题:很多漏洞甚至不在您的代码中。如果是这种情况,您必须从项目中排除该库。

    【讨论】:

      【解决方案4】:

      我的猜测是,您有崩溃是因为两个 C++ 模块之间的接口中的 std::stringstd::vector 是使用不同的编译器和运行时库编译的。

      向量和字符串的内存布局在VC6和2005之间可能会发生变化。

      当 2005 DLL 分配 StdStringContainerStdStringWrapper 类型的对象时,它会根据 2005 标头中的 stringvector 声明进行分配。

      当对这些对象(已使用 VC6 编译器和库编译)调用成员函数时,它们会采用不同的内存布局并因访问冲突而失败。

      【讨论】:

        猜你喜欢
        • 2010-12-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-30
        • 2010-11-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多