【问题标题】:Trying to Input to a member of a struct inside a vector of structs causes segmentation fault尝试向结构向量内的结构成员输入会导致分段错误
【发布时间】:2018-11-09 14:39:24
【问题描述】:

我正在做一个项目,我正在为一家假餐馆编写自动计费系统。该程序应该获取包含菜单的文本文件,将其放入结构数组或向量中,显示菜单,让客户订购并打印收据。 我正在为菜单使用结构的全局向量。

这段代码就是与问题相关的所有内容。

`

#include <iostream>
#include <fstream>
#include <vector>
//there is more code to this program, but the fault occurs very soon in the program
//and none of the rest of the code has any relevance.
//also, I don't really think that the problem is with trying to input, but I don't have enough experience to rule it out.
using namespace std;

struct menuItemType
{
  string menuItem; //this is the name of the item
  double menuPrice; // this is the price of the item
  int menuCount;
};

vector<menuItemType> menuList; //the menu can be any size so I don't know how big it will be at this point. I'm using a vector to avoid having to declare a size
// I also have 2 other functions and some extra code in main that all need to access this vector. That is why I made it global

void getData() //this function opens the text file containing the menu and tries to read in each line.
{
    ifstream input;
    input.open("inData.txt");

    input.peek();
    int i = 0;
    string item;
    double price;

    while(!input.eof())
    {
        getline(input,menuList[i].menuItem); //This is the line creating the fault.
        input >> menuList[i].menuPrice;

        i++;
        input.peek();
    }
}
int main()
{
    getData();
    return 0;
}

`

我已经尝试调试并确定分段错误不是特定于代码 sn-p 中注释的行。每当我尝试输入向量内的结构成员时,似乎都会发生错误。我也尝试过使用cin,所以我不相信文本文件流是问题所在。 文本文件如下所示:

Bacon and eggs 1.00 Muffin 0.50 Coffee 0.90

具体来说,我的问题是:为什么尝试输入向量内的结构成员会导致分段错误,我该如何解决。

对于冗长的解释和尴尬的格式,我们深表歉意。我对堆栈溢出和 c++ 都很陌生。

【问题讨论】:

  • 欢迎来到 SO :)
  • 感谢 Nox,到目前为止,SO 可能是我见过的与编程相关的最有用的网站。同样对于Rathin,我阅读了链接中的问题,并且我想我理解在循环中使用.peek().eof 通常是不好的代码礼仪,但对于为什么以及如何解决它有点过头了.
  • @Gavin 看看我的例子,设计模式将帮助您处理文件。这种方法使其更易于使用。将内容存储到一个或多个字符串、流或缓冲区中,然后在这些变量(容器)中拥有所需的所有数据后,在完成后关闭文件句柄。然后只是解析这些字符串或缓冲区的问题......解析部分是确定字符串代表什么类型的数据,然后将其转换为该类型并将其存储到您的结构或类中,然后将结构保存到向量。

标签: c++ vector struct segmentation-fault


【解决方案1】:

从文件中检索数据时;我倾向于检索单行的内容并将其存储到某个字符串、流或缓冲区并稍后解析它,或者我将检索文件的全部内容并执行相同的操作。从文件中提取数据并关闭其句柄后,我发现解析字符串更容易。我不喜欢使用非 CONST 的全局变量。此外,从文件while( file.eof() )while ( !file.eof() ) 读取文件时使用 for 循环的方式也是不好的做法,并且可能会导致以后出现许多错误、崩溃和头痛。如果您在下面查看我的函数,它所做的就是接受一个文件名并尝试打开它(如果存在)。一旦它打开,它将得到一行将其保存到一个字符串并将该字符串推入一个向量,直到没有其他内容可读取。然后它关闭文件句柄并返回。这符合具有单一职责的功能的概念。

如果您有打开文件、读取一行、解析数据、读取一行、解析数据等功能,然后关闭它;这种功能被认为承担多项任务,这可能是一件坏事。首先是性能原因。可以说,打开和读取文件本身是一项计算成本很高的任务。您还尝试动态创建对象,如果您从未检查过以验证从文件中收到的值,这可能会很糟糕。看看我下面的代码,你会看到我所指的设计模式,其中每个函数都有自己的职责。这也有助于防止file corruption

#include <vector>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <exception>

struct MenuItem {
  string menuItem; 
  double menuPrice; 
  int menuCount;
};

// This function is not used in this case but is a very helpful function
// for splitting a string into a vector of strings based on a common delimiter
// This is handy when parsing CSV files {Comma Separated Values}.
std::vector<std::string> splitString( const std::string& s, char delimiter ) {
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream( s );
    while( std::getline( tokenStream, token, delimiter ) ) {
        tokens.push_back( token );
    }

    return tokens;
}

void getDataFromFile( const char* filename, std::vector<std::string>& output ) {
    std::ifstream file( filename );
    if( !file ) {
        std::stringstream stream;
        stream << "failed to open file " << filename << '\n';
        throw std::runtime_error( stream.str() );
    }

    std::string line;
    while( std::getline( file, line ) ) {
        if ( line.size() > 0 ) 
            output.push_back( line );
    }
    file.close();
}

void parseFileData( const std::vector<std::string>& fileContents, std::vector<MenuItem> menuItems ) {
    // The first param is the contents of the file where each line
    // from the file is stored as a string and pushed into a vector.

    // Here you need to parse this data. The second parameter is the
    // vector of menu items that is being passed by reference.

    // You can not modify the fileContents directly as it is const and read only
    // however the menuItems is passed by reference only so you can update that

    // This is where you will need to go through some kind of loop and get string
    // of text that will stored in your MenuItem::menuItem variable.
    // then the next string will have your price. Here you showed that your
    // text file has `$` in front of the value. You will then have to strip this out 
    // leaving you with just the value itself. 
    // Then you can use `std::stod( stringValue ) to convert to value, 
    // then you can save that to MenuTiem::menuPrice variable.

    // After you have the values you need then you can push back this temp MenuItem
    // Into the vector of MenuItems that was passed in. This is one iteration of
    // your loop. You continue this until you are done traversing through the fileContents vector.


    // This function I'll leave for you to try and write.        
}

int main() {
    try {
        std::vector<std::string> fileConents;
        getDataFromFile( "test.txt", fileConents );
        std::vector<MenuItem> data; // here is the menu list from your example
        generateVectors( fileConents, data );

        // test to see if info is correct
        for( auto& d : data ) {
            std::cout << data.menuItem << " " << data.menuPrice << '\n';
        }

    } catch( const std::runtime_error& e ) {
        std::cerr << e.what() << '\n';
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

至于您的错误或崩溃,您可能正在访问超出向量末尾的索引,或者您试图使用向量中包含无效数据的内容。

【讨论】:

  • 好的,这是有道理的,它会使用更多的资源。至于我的程序,它现在运行,但是当它尝试从文本文件中获取菜单项及其价格时,它返回第一项及其价格,但之后它返回空白,即使还有 2 个项目。你上面提到的可能是原因吗?此外,$ 是一个错误。我的文件实际上没有。
  • @Gavin 使用您的调试器并一次检查一行推入向量中的字符串。您可能需要处理空格、空字符、结束行或回车。如果在函数返回给调用者之前从文件中读取的字符串向量看起来是正确的,那么问题可能出在正在解析数据的函数中。
  • 我还没有做解析功能。我确实修改了循环,所以我不再有.eof。我使用getline 来检查更多行。我遇到了一个奇怪的问题。首先,当我最初调试时,在返回第一个项目和价格后 getline 返回 "" 而不是结束行或空格。其次,由于某种原因,我无法再调试了,因为我遇到了分段错误,但是如果我正常运行,我就不会收到错误。
  • 调试显示Program terminated with signal SIGSEGV, Segmentation fault. The program no longer exists.
  • @Gavin 我不确定,我不知道您使用的是什么编译器,也不知道您正在运行什么操作系统。
【解决方案2】:

如果您查看operator[] of a vector,然后检查异常部分,它会告诉您如果 n 大于向量的大小,它实际上是未定义的行为。您可能想推回您之前创建的项目。

我通常更喜欢vector::at,因为它经过边界检查,如果请求的位置超出范围,则通过抛出 out_of_range 异常发出信号。

【讨论】:

  • 确实,使用menuList.push_back 可以解决问题。
  • 那么这是否意味着创建一个临时结构变量然后使用push_back 将其添加到向量中,而不是尝试将数据直接读取到向量中会更好?
  • 如果你的意思是工作更好,那么是的! :) 如果您发现某个答案确实有帮助并回答了您的问题,您可以将其标记为答案 :),它也会给我一些分数。
  • 感谢您的帮助。我尝试这样做,但它停止给我一个分段错误。相反,它现在给了我一个“bad_alloc”错误。我调试并发现.eof.peek() 循环运行得不太好,现在我有一个无限循环。这可能就是Rathin提到它的原因。
【解决方案3】:

首先从 inData.txt 中删除 "$" 然后我建议像这样使用 while(getline(input, item)) :

#include <iostream>
#include <fstream>
#include <vector>
#include <math.h>
//there is more code to this program, but the fault occurs very soon in the program
//and none of the rest of the code has any relevance.
//also, I don't really think that the problem is with trying to input, but I don't have enough experience to rule it out.
using namespace std;

struct menuItemType
{
  string menuItem; //this is the name of the item
  double menuPrice; // this is the price of the item
  int menuCount;
};

vector<menuItemType*> menuList; //the menu can be any size so I don't know how big it will be at this point. I'm using a vector to avoid having to declare a size
// I also have 2 other functions and some extra code in main that all need to access this vector. That is why I made it global

void getData() //this function opens the text file containing the menu and tries to read in each line.
{
    ifstream input;
    input.open("inData.txt");

    int i = 0;
    string item;
    double price;

    while(getline(input, item))
    {
        menuList.push_back(new menuItemType);
        menuList[i]->menuItem = item;
        getline(input,item);
        menuList[i]->menuPrice = atof((char*)item.c_str()); //math.h
        i++;
    }
}
int main()
{
    getData();
    for(menu : menuList)
    {
        cout << menu->menuItem << ": " << menu->menuPrice << endl;
        delete menu; //cleaning memory
    }
    return 0;
}

【讨论】:

  • 如果您查看我的实现,甚至不需要使用指针或动态内存。我将向量移到 main 内部,因为我不喜欢使用全局变量,除非它们是 const 值。话虽如此,我只是通过引用将向量传递给函数,它将填充它。向量本身将其内容存储在托管动态内存中。现在,如果 OP 需要将其置于全局范围内,他们应该将此向量包装在 shared_ptr 中。
  • @FrancisCugler 你是对的,但我会保留我的答案,因为你的帖子可能对新手来说信息太多
  • @PatrykMerchelski 曾经我还是个新手,在某些方面我仍然是 C++ 语言的某些方面,在其他方面我是中级到高级,几乎是专家。我是 100% 自学成才的,所以我知道你来自哪里;但是,我认为应该尽早教新手和学习它的人。我希望我在刚开始学习 C++ 时就知道这一点!
  • @FrancisCugler 观点不错,我会将其视为建议
  • @Francis Cugler 我发现了问题。 C++ 似乎不喜欢在获得整数输入后获得字符串输入。从文本文件输入价格后,我使用了ifstream::ignore(256,'\n');。除了一些尴尬的缩进之外,该程序运行良好。感谢您的帮助!(也感谢其他人)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-02-19
  • 1970-01-01
  • 1970-01-01
  • 2016-10-22
  • 2014-12-27
  • 2010-10-09
  • 2012-10-16
相关资源
最近更新 更多