Это использование 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() называется.

2

Другие решения

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

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(),

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

5

Как уже говорилось, код не определен, так как указатель назначен 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() возвращается по умолчанию, таким образом, избегая висячих проблем.

1
По вопросам рекламы [email protected]