Home > Back-end >  Why dynamic_cast from reference causes a segmentation fault?
Why dynamic_cast from reference causes a segmentation fault?

Time:07-17

I have an expr_t base class, from which ident_t is derived. I wrote some to_string overloads to display differently between expr_t and ident_t:

#include <iostream>
#include <string>

struct expr_t {
  virtual ~expr_t() {}
};

struct ident_t : public expr_t {
  std::string name;

  ident_t(std::string name) : name(name) {}
};
std::string to_string(expr_t& v) {
  if (auto* id = dynamic_cast<ident_t*>(&v)) {
    return to_string(*id);
  }
  return "error";
}
std::string to_string(ident_t& v) {
  return v.name;
}

int main() {
  expr_t* b = new ident_t("c");
  std::cout << to_string(*b) << std::endl; // segfault
  delete b;
  return 0;
}

However, no output is generated and seg-fault occurs when debugging with GDB.

CodePudding user response:

The problem is that at the point of the call return to_string(*id) the compiler doesn't have a declaration for the second overload std::string to_string(ident_t& v). Thus the same first version will be recursively called eventually resulting in a seg fault.

To solve this you can either move the second overload's definition before the first overload or forward declare the second overload before the definition of the first overload as shown below:

//moved the 2nd overload above first overload
std::string to_string(ident_t& v) {
  return v.name;
}

std::string to_string(expr_t& v) {
  if (auto* id = dynamic_cast<ident_t*>(&v)) {
    return to_string(*id); //now compiler knows about std::string to_string(ident_t& v)
  }
  return "error";
}

Demo

Method 2

//forward declaration for 2nd overload
std::string to_string(ident_t& v);

std::string to_string(expr_t& v) {
  if (auto* id = dynamic_cast<ident_t*>(&v)) {
    return to_string(*id); //now compiler knows about std::string to_string(ident_t& v)
  }
  return "error";
}
//definition for 2nd overload
std::string to_string(ident_t& v) {
  return v.name;
}

Demo

CodePudding user response:

As mentioned in the comments, at the point where you call (or try to call) the version of to_string that takes an ident_t& argument, there has been neither definition nor declaration of such an overload. Thus, that call becomes infinitely recursive, causing (eventually) a crash.

To resolve the issue either: (a) move the definition of the ident_t&-taking overload to before the expr_t& version; or (b) at least provide a declaration of that overload before the call:

std::string to_string(ident_t& v); // Declaration of the other version ...
std::string to_string(expr_t& v)
{
    if (auto* id = dynamic_cast<ident_t*>(&v)) {
        return to_string(*id);     // ... which can now be used here!
    }
    return "error";
}

(You could even add that declaration immediately before the call – i.e. inside the if (auto* id ...) block; however, although such local function declarations are technically legal, many C programmers don't like them.)

  •  Tags:  
  • c
  • Related