Это использование c_str с неопределенным поведением исключения?

Я видел несколько похожих фрагментов кода, которые выглядели так:

struct MyExcept : std::exception {
    explicit MyExcept(const char* m) noexcept : message{m} {}

    const char* what() const noexcept override {
        return message;
    }

    const char* message;
};

void foo() {
    std::string error;

    error += "Some";
    error += " Error";

    throw MyExcept{error.c_str()};
}

int main() {
    try {
        foo();
    } catch (const MyExcept& e) {
        // Is this okay?
        std::cout << e.message << std::endl;
    }
}

В строке после комментария Is this okay?мы читаем строку в стиле c, которая была выделена в foo использовать функцию std::string, Так как строка разрушается с разматыванием стека, это неопределенное поведение?


Если это действительно неопределенное поведение, что если мы заменим main работать с этим?

int main() {
    foo();
}

Поскольку здесь нет перехвата, компилятору не нужно разматывать стек, и все же выводить результат what() в консоли и прервите программу. Так это все еще неопределенное поведение?

3 ответа

Решение

Ваш первый фрагмент имеет неопределенное поведение. [exception.ctor]/1:

Когда управление переходит от точки, где исключение выдается обработчику, деструкторы вызываются процессом, указанным в этом разделе, который называется разматыванием стека.

Здесь деструктор или error называется, вызывая c_str() стать висящим указателем. Позже разыменовываем его, когда вы используете std::cout например, неопределенное поведение.

Ваш второй фрагмент в порядке. Нет никаких причин, почему это было бы неопределенным поведением. Вы никогда не звоните what или сделать что-нибудь еще, что может привести к неопределенному поведению. Единственное, что не определено Стандартом, - это если раскрутка стека происходит или нет, [except.terminate]/2:

В ситуации, когда соответствующий обработчик не найден, определяется реализацией, будет ли стек разматываться перед std​::​terminate() называется.

Да, это неопределенное поведение. Вы работаете с висящим указателем.

void foo() {
    std::string error;

    error += "Some";
    error += " Error";

    throw MyExcept{error.c_str()};
} // <<  error goes out of scope here and so does the pointer returned
  //     from c_str()

Поскольку здесь нет перехвата, компилятору не нужно разматывать стек, и все же выводить результат what() в консоли и прервите программу. Так это все еще неопределенное поведение?

Поскольку реализация по умолчанию будет использовать std::terminate и в свою очередь зовет std::abort() это может быть все еще неопределенное поведение, потому что большинство реализаций стандартного обработчика будут пытаться разыменовать what(),

Вы можете установить свои собственные обработчики, чтобы избежать этого.

Как уже говорилось, код не определен, так как указатель назначен message осталось болтаться

std::runtime_error уже предоставляет решение этой проблемы. Вызовите его конструктор, который принимает std::string в качестве входных данных, и не переопределять what() совсем:

struct MyExcept : std::runtime_error {
    explicit MyExcept(const std::string & m) noexcept : std::runtime_error(m) {}
};

void foo() {
    std::string error;

    error += "Some";
    error += " Error";

    throw MyExcept(error);
}

int main() {
    try {
        foo();
    }
    catch (const MyExcept& e) {
        std::cout << e.what() << std::endl;
    }
}

std::runtime_error имеет внутренний std::string чьи данные what() возвращается по умолчанию, таким образом, избегая висячих проблем.

Другие вопросы по тегам