Construct standard exceptions with null pointer argument and impossible postconditions

感情迁移 提交于 2020-05-14 16:43:11

问题


Consider the following program:

#include<stdexcept>
#include<iostream>

int main() {
    try {
        throw std::range_error(nullptr);
    } catch(const std::range_error&) {
        std::cout << "Caught!\n";
    }
}

GCC and Clang with libstdc++ call std::terminate and abort the program with the message

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Clang with libc++ segfaults on construction of the exception.

See godbolt.

Are the compilers behaving standard-conform? The relevant section of the standard [diagnostics.range.error] (C++17 N4659) does say that std::range_error has a const char* constructor overload which should be preferred over the const std::string& overload. The section also does not state any preconditions on the constructor and only states the postcondition

Postconditions: strcmp(what(), what_­arg) == 0.

This postcondition always has undefined behavior if what_arg is a null pointer, so does this mean that my program also has undefined behavior and that both compilers act conformant? If not, how should one read such impossible postconditions in the standard?


On second thought, I think it must mean undefined behavior for my program, because if it didn't then (valid) pointers not pointing to null-terminated strings would also be allowed, which clearly makes no sense.

So, assuming that is true, I would like to focus the question more on how the standard implies this undefined behavior. Does it follow from the impossibility of the postcondition that the call also has undefined behavior or was the precondition simply forgotten?


Inspired by this question.


回答1:


From the doc:

Because copying std::range_error is not permitted to throw exceptions, this message is typically stored internally as a separately-allocated reference-counted string. This is also why there is no constructor taking std::string&&: it would have to copy the content anyway.

This shows why you get segfault, the api does really treat it as a real string. In general in cpp if something was optional, there will be an overloaded constructor/function that does not take what it does not need. So passing nullptr for a function that does not document something to be optional is going to be undefined behavior. Usually API's don't take pointers with the exception for C strings. So IMHO it is safe to assume passing nullptr for a function that expects a const char *, is going to be undefined behavior. Newer APIs may prefer std::string_view for those cases.

Update:

Usually it is fair to assume a C++ API taking a pointer to accept NULL. However C strings are a special case. Until std::string_view, there was no better way to pass them efficiently. In general for an API accepting const char *, assumption should be is that it has to be a valid C string. i.e a pointer to a sequence of chars that terminates with a '\0'.

range_error could validate that the pointer is not nullptr but it cannot validate if it terminates with a '\0'. So it better not do any validation.

I don't know the exact wording in the standard, but this pre-condition is probably assumed automatically.




回答2:


This goes back to the basic question is it OK to create a std::string from nullptr? and what should it do it is?

www.cplusplus.com says

If s is a null pointer, if n == npos, or if the range specified by [first,last) is not valid, it causes undefined behavior.

So when

throw std::range_error(nullptr);

is called the implementation tries to make something like

store = std::make_shared<std::string>(nullptr);

which is undefined. Which I would consider a bug (without having read the actual wording in the standard). Instead the libery developers could have made something like

if (what_arg)
  store = std::make_shared<std::string>(nullptr);

But then the catcher has to check for nullptr in what(); or it will just crash there. So the std::range_error should assign either an empty string or "(nullptr)" like some other languages does.



来源:https://stackoverflow.com/questions/60924295/construct-standard-exceptions-with-null-pointer-argument-and-impossible-postcond

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!