Создание стандартных исключений с аргументом нулевого указателя и невозможными постусловиями

9

Рассмотрим следующую программу:

#include<stdexcept>
#include<iostream>

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

GCC и Clang с помощью libstdc ++ вызывают std::terminateи отменяют программу с сообщением

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

Clang с libc ++ segfaults при построении исключения.

Вижу Годболт .

Ведут ли компиляторы стандартное соответствие? В соответствующем разделе стандарта [ Diagnics.range.error ] (C ++ 17 N4659) сказано, что std::range_errorимеется const char*перегрузка конструктора, которая должна быть предпочтительнее const std::string&перегрузки. В разделе также не указываются никакие предварительные условия для конструктора, а только указывается постусловие.

Постусловия : strcmp(what(), what_­arg) == 0.

Это постусловие всегда имеет неопределенное поведение, если what_argявляется нулевым указателем, значит ли это, что моя программа также имеет неопределенное поведение и что оба компилятора действуют согласованно? Если нет, то как следует читать такие невозможные постусловия в стандарте?


Во-вторых, я думаю, что это должно означать неопределенное поведение для моей программы, потому что, если это не так, то (допустимые) указатели, не указывающие на строки с нулевым символом в конце, также будут разрешены, что явно не имеет смысла.

Итак, предполагая, что это правда, я хотел бы сосредоточить вопрос больше на том, как стандарт подразумевает это неопределенное поведение. Из невозможности постусловия следует, что вызов также имеет неопределенное поведение, или предусловие было просто забыто?


Вдохновлен этим вопросом .

грецкий орех
источник
Похоже, что std :: range_error позволяет хранить вещи по ссылке, поэтому я не удивлюсь. Вызов, what()когда nullptrпрошло, вероятно, вызовет проблемы.
Чипстер
@Chipster Я не уверен, что именно вы имеете в виду, но подумав об этом снова, я думаю, что это должно быть неопределенное поведение. Я отредактировал свой вопрос, чтобы больше сосредоточиться на том, как стандартная формулировка подразумевает неопределенное поведение.
грецкий орех
Если это nullptrбудет принято, я думаю, что what()в какой-то момент придется разыменовать его, чтобы получить значение. Это было бы разыменованием a nullptr, которое в лучшем случае проблематично, а наверняка к краху - хуже всего.
Чипстер
Я согласен, хотя. Это должно быть неопределенное поведение. Тем не менее, положить это в адекватный ответ, объясняющий, почему выходит за рамки моих навыков.
Чипстер
Я думаю, что предполагается, что это предварительное условие, что аргумент указывает на допустимую строку C, поскольку strcmpиспользуется для описания значения what_arg. Это то, что в любом случае говорится в соответствующем разделе стандарта C , на который ссылается спецификация <cstring>. Конечно, формулировка может быть более понятной.
LF

Ответы:

0

Из документа :

Поскольку копирование std :: range_error не разрешает генерировать исключения, это сообщение обычно хранится внутри как отдельно выделенная строка с подсчетом ссылок. По этой же причине нет конструктора, принимающего std :: string &&: в любом случае ему пришлось бы копировать содержимое.

Это показывает, почему вы получаете segfault, API действительно обрабатывает его как настоящую строку. В общем случае в cpp, если что-то было необязательным, будет перегруженный конструктор / функция, которая не берет то, что ей не нужно. Таким образом, передача nullptrфункции, которая не документирует что-то необязательное, будет неопределенным поведением. Обычно API не используют указатели, за исключением строк C. Так что ИМХО можно предположить, что передача nullptr для функции, которая ожидает const char *, будет неопределенным поведением. Новые API могут предпочесть std::string_viewдля этих случаев.

Обновить:

Обычно справедливо предположить, что C ++ API принимает указатель для принятия NULL. Однако строки C являются особым случаем. Пока std::string_viewне было лучшего способа их эффективно передать. В общем случае для принятия API const char *предполагается, что это должна быть допустимая строка C. то есть указатель на последовательность chars, оканчивающуюся на '\ 0'.

range_errorможет подтвердить, что указатель не является, nullptrно он не может проверить, если он заканчивается на «\ 0». Так что лучше не делать никаких проверок.

Я не знаю точную формулировку в стандарте, но это предварительное условие, вероятно, предполагается автоматически.

Балки
источник
-2

Это возвращает нас к основному вопросу: можно ли создавать std :: string из nullptr? и что он должен это сделать?

www.cplusplus.com говорит

Если s является нулевым указателем, если n == npos или диапазон, указанный в [first, last), недопустим, это вызывает неопределенное поведение.

Так когда

throw std::range_error(nullptr);

называется реализация пытается сделать что-то вроде

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

который не определен. Что я бы посчитал ошибкой (не прочитав фактическую формулировку в стандарте). Вместо этого разработчики свободы могли бы сделать что-то вроде

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

Но тогда ловец должен проверить на наличие nullptr, what();иначе он просто там вылетит. Поэтому std::range_errorследует присваивать либо пустую строку, либо "(nullptr)", как это делают некоторые другие языки.

Surt
источник
«Это возвращает нас к основному вопросу: можно ли создавать std :: string из nullptr?» - Я не думаю, что это так.
Конрад Рудольф
Я не думаю, что стандарт указывает где-либо, что исключение должно сохранять a, std::stringи std::stringконструктор не должен выбираться с помощью разрешения перегрузки.
грецкий орех
const char *Перегрузки выбирается разрешения перегрузки
MM