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

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

#include<stdexcept>
#include<iostream>

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

G CC и 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 on Конструкция исключения.

См. godbolt .

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

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

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


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

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


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

Ответы [ 2 ]

0 голосов
/ 07 апреля 2020

Из do c:

Поскольку копирование 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. то есть указатель на последовательность char s, оканчивающуюся на '\ 0'.

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

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

0 голосов
/ 02 апреля 2020

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

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

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

Поэтому, когда вызывается

throw std::range_error(nullptr);

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

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

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

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

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

...