【问题标题】:Why is this segfaulting?为什么这是段错误?
【发布时间】:2020-02-10 15:54:40
【问题描述】:

我有以下构建器来构建Tokens

// token_builder.h
class TokenBuilder
{
    Keyword m_keyword;
    TokenType m_type;
    std::string m_symbol;

public:
    TokenBuilder& set_keyword(Keyword);
    TokenBuilder& set_toktype(TokenType);
    TokenBuilder& set_symbol(std::string);
    std::unique_ptr<Token>&& build();
};
// token_builder.cpp
TokenBuilder& TokenBuilder::set_keyword(Keyword k)
  {
      m_keyword = k;
      return *this;
  }

  TokenBuilder& TokenBuilder::set_toktype(TokenType t)
  {
      m_type = t;
      return *this;
  }

  TokenBuilder& TokenBuilder::set_symbol(std::string sym)
  {
      m_symbol = sym; // This is the point where it segfaults
      return *this;
  }

  std::unique_ptr<Token>&& TokenBuilder::build()
  {
      std::unique_ptr<Token> token;
      token->m_keyword = m_keyword;
      token->m_type = m_type;
      token->m_symbol = m_symbol;
      return std::move(token);
  }

程序在我试图通过调用TokenBuilder::set_symbol 初始化m_symbol 时以SIGSEGV 停止。这是main 的样子:

int main()
{
    auto token = Token::builder()
            .set_keyword(Keyword::IF)
            .set_toktype(TokenType::ID)
            .set_symbol("if")
            .build();

    std::cout << token->get_symbol() << std::endl;
}

如您所见,调用set_keywordset_toktype 不会导致任何错误。但是将文字“if”(或std::string 对象)传递给set_symbol 会导致程序崩溃。按值传递是故意的。进入set_symbol 并分析TokenBuilder 的状态并没有透露任何信息。正如预期的那样,m_keyword 设置为 Keyword::IFm_type 设置为 TokenType::ID。这是崩溃的行:m_symbol = sym

上面的程序可能还有其他重要的问题(我是否正确使用std::move?在很长一段时间后返回C++),但这种感觉就像一个菜鸟会犯的愚蠢错误。我在 Windows 10(64 位)上使用 MinGW-w64 编译器。

这是方便复制粘贴到您的机器的完整代码:

// src/include/token_builder.h
#ifndef TOKEN_BUILDER_H
#define TOKEN_BUILDER_H

#include <memory>
#include "token.h"

namespace funk
{
class Token;

class TokenBuilder
{
    Keyword m_keyword;
    TokenType m_type;
    std::string m_symbol;

public:
    TokenBuilder& set_keyword(Keyword);
    TokenBuilder& set_toktype(TokenType);
    TokenBuilder& set_symbol(std::string);
    std::unique_ptr<Token>&& build();
};

}

#endif
// src/token_builder.cpp
#include "include/token.h"
#include "include/token_builder.h"

namespace funk
{
  TokenBuilder& TokenBuilder::set_keyword(Keyword k)
  {
      m_keyword = k;
      return *this;
  }

  TokenBuilder& TokenBuilder::set_toktype(TokenType t)
  {
      m_type = t;
      return *this;
  }

  TokenBuilder& TokenBuilder::set_symbol(std::string sym)
  {
      m_symbol = sym;
      return *this;
  }

  std::unique_ptr<Token>&& TokenBuilder::build()
  {
      std::unique_ptr<Token> token;

      token->m_is_keyword = m_keyword != Keyword::_NONE_;
      token->m_keyword = m_keyword;
      token->m_type = m_type;
      token->m_symbol = m_symbol;

      return std::move(token);
  }
}
// src/include/token.h
#ifndef TOKEN_H
#define TOKEN_H
#include <memory>
#include "keyword.h"
#include "token_type.h"
#include "token_builder.h"

namespace funk
{
  class TokenBuilder;

  class Token
  {
      friend class TokenBuilder;

      Keyword m_keyword;
      bool m_is_keyword;
      std::string m_symbol;
      TokenType m_type;

      Token();

  public:

      Keyword& get_keyword ();
      bool is_keyword();
      std::string& get_symbol();
      TokenType& get_type();
      static TokenBuilder&& builder();
  };
}

#endif // END TOKEN_H
// src/token.cpp
#include <string>
#include <memory>
#include "include/keyword.h"
#include "include/token_builder.h"
#include "include/token_type.h"

namespace funk
{
  Keyword& Token::get_keyword()
  {
    return m_keyword;
  }

  bool Token::is_keyword()
  {
    return m_is_keyword;
  }

  std::string& Token::get_symbol()
  {
    return m_symbol;
  }

  TokenType& Token::get_type()
  {
    return m_type;
  }

  TokenBuilder&& Token::builder()
  {
    TokenBuilder builder;
    return std::move(builder);
  }
}

// src/main.cpp
#include <iostream>
#include "include/buffered_reader.h"
#include "include/token.h"
#include "include/token_builder.h"

int main()
{
    using namespace funk;

    auto token = Token::builder()
            .set_keyword(Keyword::IF)
            .set_toktype(TokenType::ID)
            .set_symbol("if")
            .build();

    std::cout << token->get_symbol() << std::endl;
}

注意:我尽我所能在 StackOverflow 上查找了类似的问题,但没有找到与我的情况相符的问题。如果您认为它与您之前遇到的问题相似,请随时标记重复并将链接发布到原始问题。谢谢!

【问题讨论】:

  • 在 gdb 下运行你的程序,然后运行回溯。它会告诉你你在哪条线上进行段错误。您提供的代码太多了,您最好学习如何调试自己的程序,而不是依赖 StackOverflow 为您调试它们。
  • Token::builder() 返回对局部变量的引用。一旦函数返回,变量就会被销毁并且引用变得悬空。调用者使用该引用会表现出未定义的行为。
  • @Gillespie 我知道哪一行导致崩溃。阅读问题。
  • @DevashishJaiswal 抱歉,当我向下滚动时,它在您发布的代码墙中丢失了。我建议下次尝试制作stackoverflow.com/help/minimal-reproducible-example
  • TokenBuilder::build 中,您永远不会将token 设置为引用有效的Token。它被有效地取消引用,同时仍然是一个空指针。

标签: c++ c++11 segmentation-fault stdstring


【解决方案1】:
  std::unique_ptr<Token>&& TokenBuilder::build()
  {
      std::unique_ptr<Token> token;
      // ...
      return std::move(token);
  }

您返回对局部变量的引用。在函数结束时,局部变量被销毁,返回的引用将指向一个超出其生命周期的对象。当您稍后尝试通过悬空引用访问不存在的对象时,程序的行为是未定义的。

【讨论】:

  • 我怀疑那部分我错了。但是坠机不应该发生在其他地方吗?我的意思是,如果我在调用set_keywordset_type 之前调用set_symbol,那么崩溃会在调用set_typeset_keyword 之前发生。
  • 虽然这也是真的,但这不是段错误的原因。显示的代码中有多个错误,并且在此之前发生了致命的段错误。其原因要明显得多。
  • @DevashishJaiswal 程序的行为未定义。无法保证它何时崩溃或是否完全崩溃。
  • @DevashishJaiswal 为了帮助找到所有错误,我建议创建一个minimal reproducible example
  • @DevashishJaiswal std::move'ing 返回值也是pessimization,简单的return std::unique_ptr
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多