Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How come `e.what()` prints "bad allocation"?

The new expression in the try block throws a bad_allocexception in my computer.

Note that the catch clause receives an exception object by value, not by reference. How come e.what() prints "bad allocation" ? I thought it would be sliced.

#include <iostream>

int main()
{
    try
    {
        int* p = new int[0x1F000000];
    }
    catch(std::exception e)
    {
        std::cout << e.what() << std::endl;
    }
}
like image 344
Belloc Avatar asked Nov 30 '11 23:11

Belloc


1 Answers

Visual Studio (Dinkumware?) uses an implementation of std::exception that contains internal storage† for the message. (Complete with a non-standard constructor that accepts a string.)

Because of this, no virtual dispatch is actually needed to get the error message, it survives any slicing.

A more orthodox implementation would indeed print a generic exception message, because the derived object was sliced off. (Effectively, MS has made std::exception and std::runtime_error equivalent. There's nothing wrong with this, since the return value of std::exception::what is implementation-defined, but it explains your results.)


†Internal storage here is used loosely. It doesn't have an internal buffer, but it has a const char* and a bool. The const char* points to the message (the return value of what()), and the bool is a flag determining if the buffer should be deleted.

It's like this:

class msvc_exception // for exposition
{
public:
    msvc_exception(const char* msg) :
    mMsg(msg),
    mDoDelete(false)
    {}

    msvc_exception(const std::string& msg) :
    mMsg(copy_string(msg)),
    mDoDelete(true)
    {}

    virtual ~msvc_exception()
    {
        if (mDoDelete)
            delete [] mMsg;
    }

    virtual const char* what() const throw()
    {
        return mMsg ? mMsg : "unknown";
    }

private:
    const char* copy_string(const std::string& str)
    {
        const char* result = new char[str.size() + 1];

        std::copy(str.begin(), str.end(), result);
        result[str.size()] = 0; // null-terminate

        return result;
    }
};

You see now that bad_alloc works like this:

    class msvc_bad_alloc : // for exposition
        public msvc_exception
    {
    public:
        msvc_bad_alloc() :
        msvc_exception("bad_alloc") // note: a static string, no dynamic storage
        {}
    };

Slicing doesn't affect the message because the message "exists" in the base class.

Other compilers, like GCC and LLVM, implement it a bit more straight-forwardly:

class orthodox_exception
{
public:
    orthodox_exception(){}
    virtual ~orthodox_exception() {}

    virtual const char* what() const throw()
    {
        return "orthodox_exception";
    }
};

class orthodox_bad_alloc :
    public orthodox_exception
{
public:
    const char* what() const throw()
    {
        return "orthodox_bad_alloc";
    }
};

Here, slicing would affect your outcome. (That said, after all this: always catch by reference.)

like image 176
GManNickG Avatar answered Oct 02 '22 07:10

GManNickG