【问题标题】:C++: Vector element assignment causing Access violationC++:向量元素赋值导致访问冲突
【发布时间】:2018-07-11 05:06:18
【问题描述】:

在 C++ 中将字符串分解为单独的行和参数(作为 2D 向量)时,在尝试解析函数之间的向量时可能会产生访问冲突的有趣问题。 在代码示例中,已经进行了许多尝试来确保传入和传出函数的数据是独立的对象,而不是引用。

segregate.hpp

#pragma once

#include <vector>
#include <string>

/*
  Purpose:
    To to take a whole file as a string,
    and break it up into individual words
*/

namespace Segregate {
  // Module types
  typedef std::vector< std::string > ParamArray;
  struct StrCommand{
    unsigned long line;
    ParamArray param;
  };
  typedef std::vector< StrCommand > StrCommands;

  bool IsParamBreak(char val);
  bool IsLineBreak(char val);

  ParamArray Parameterize(std::string str);
  StrCommands Fragment(std::string str);
}


#include "./segregate.cpp"

segregate.cpp

#include "./segregate.hpp"

namespace Segregate{
    bool IsParamBreak(char val){
        if (val == ' '){
            return true;
        }else if (val == '\t'){
            return true;
        }

        return false;
    };
    bool IsLineBreak(char val){
        if (val == '\n'){
            return true;
        }

        return false;
    };

    // Splits a single line into individual parameters
    ParamArray Parameterize(std::string str){
        str.append(" "); // Ensures that the loop will cover all segments
        unsigned long length = str.size();
        unsigned long comStart = 0;
        ParamArray res;


        // Ignore carrage returns
        //  Windows artifact
        if (str[0] == '\r'){
            comStart = 1;
        }


        // Ignore indentation
        //  Find the start of actual content
        while (comStart < length && IsParamBreak(str[comStart])){
            comStart++;
        }

        // Count the number of parameters
        unsigned long vecLen = 0;
        for (unsigned long i=comStart; i<length; i++){
            if ( IsParamBreak(str[i]) ){
                vecLen++;
            }
        }
        res.reserve(vecLen);


        // Scan will fail if there is no data
        if (length == 0){
            return res;
        }


        // Slice the the string into parts
        unsigned long toIndex = 0;
        unsigned long cursor = comStart;
        for (unsigned long i=cursor; i<length; i++){
            if (IsParamBreak(str[i]) == true){
                // Transfer the sub-string to the vector,
                //  Ensure that the data is it's own, and not a reference
                res[toIndex].reserve(i-cursor);

                // Error here
                res[toIndex].assign( str.substr(cursor, i-cursor) );

                cursor = i+1;
                toIndex++;
            }
        }

        return res;
    };


    StrCommands Fragment(std::string str){
        str.append("\n"); // Ensures that the loop will cover all segments
        unsigned long length = str.size();

        // Result
        StrCommands res;


        // Count lines
        //  Ignoring empty lines
        unsigned long vecLen = 1;
        for (unsigned long i=0; i<length; i++){
            if (IsLineBreak(str[i])){
                vecLen++;
            }
        }
        res.reserve(vecLen);


        // Ignore 'empty' strings as they may cause errors
        if (vecLen == 0){
            return res;
        }


        // Read lines
        unsigned long toIndex = 0;
        unsigned long cursor = 0;
        for (unsigned long i=0; i<length; i++){
            if (IsLineBreak(str[i])){

                // Error here
                res[toIndex].param = ParamArray(  Parameterize( std::string(str.substr(cursor, i-cursor)) )  );
                res[toIndex].line = i+1;

                // Ignore blank lines
                if (res[toIndex].param.size() == 0){
                    vecLen--;
                }else{
                    toIndex++;
                }
                cursor = i+1;
            }
        }


        // Shrink the result due to undersizing for blank lines
        res.reserve(vecLen);

        return res;
    };
}

内存访问违规通常发生在第 66 行和第 108 行(当元素数据本地存储在向量中时)。它似乎发生在分配阶段,通过使用中间临时变量在解析后直接存储结果来推断。 该错误也可能在 vector::reserve() 期间发生,但发生频率较低。


注意:在 Windows 上没有直接的错误消息:

在 fiber.exe 中的 0x00A20462 处引发异常:0xC0000005:访问 违规读取位置0xBAADF009。

仅在使用“C/C++ Extension for Visual Studio Code”调试时可见,而不是在正常终端执行中。
但是在 Ubuntu 上它输出:

分段错误(核心转储)

【问题讨论】:

  • 使用std::vector::resize 而不是std::vector::reserve
  • 内存访问违规通常发生在第 66 行和第 108 行——好吧,让我数一数。 1、2、3、4,……算了。 66是哪一行,108是哪一行?
  • 您正在使用 reserve() 分配内存但不构造任何对象,因此在第 72 行您分配内存但向量的 size() 仍然为 0,然后在第 88 行您尝试使用不存在的索引 0 处的向量,因为 res 的大小仍然为 0。
  • 您稍微误解了once 的作用。它保证每个翻译单元(一个编译的源文件)只包含一个。如果您正在编译多个源文件,那么所有的赌注都没有了。每个翻译单元可以包含一次文件,当链接器将翻译单元组装成单个程序时会导致混乱。
  • vector 包含指向内存中某处动态分配的块的指针。 resize 将扩展 vector 的存储块中任何未使用的容量(不用担心覆盖有效数据,那里什么都没有)或者如果新大小大于容量,vector 将分配一个新的块至少足够大以存储请求的元素数量并从旧块复制数据并在释放旧块之前填充未使用的数据。除非您有一个不应该写入内存的错误,否则不会覆盖和丢失任何数据。

标签: c++ vector c++14 access-violation


【解决方案1】:

你在你的向量上调用reserve,它分配内存来存储你的对象,但不构造它们。然后,当您尝试使用尚未构造的对象的方法时,它可能会崩溃。

有两种可能的解决方案,要么调用resize 而不是reserve,要么调用push_back 在向量末尾构造新对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-23
    • 1970-01-01
    • 2010-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多