Подтвердить что ты не робот

Действительно ли std:: stoi безопасен?

У меня был прекрасный разговор с кем-то о падениях std::stoi. Если говорить откровенно, он использует std::strtol внутренне и бросает, если это сообщает об ошибке. По их мнению, std::strtol не должен сообщать об ошибке для ввода "abcxyz", вызывая stoi не бросать std::invalid_argument.

Прежде всего, вот две программы, проверенные на GCC о поведении этих случаев:
strtol
stoi

Оба из них показывают успех на "123" и сбой на "abc".


Я посмотрел в стандарте, чтобы получить дополнительную информацию:

§ 21.5

Throws: invalid_argument if strtol, strtoul, strtoll, or strtoull reports that  
no conversion could be performed. Throws out_of_range if the converted value is  
outside the range of representable values for the return type.

Это суммирует поведение, полагающееся на strtol. Как насчет strtol? Я нашел это в проекте C11:

§7.22.1.4

If the subject sequence is empty or does not have the expected form, no  
conversion is performed; the value of nptr is stored in the object  
pointed to by endptr, provided that endptr is not a null pointer.

Учитывая ситуацию прохождения в "abc", стандарт C определяет, что nptr, который указывает на начало строки, будет храниться в endptr, указатель прошел. Это похоже на тест, Кроме того, 0 должно быть возвращено, как указано в следующем:

§7.22.1.4

If no conversion could be performed, zero is returned.

В предыдущей ссылке говорилось, что никакого преобразования не будет выполнено, поэтому он должен вернуть 0. Эти условия теперь соответствуют стандарту С++ 11 для stoi throwing std::invalid_argument.


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

Итак, после всего этого, я где-то ошибся? Мне кажется, что у меня есть хорошее доказательство того, что это исключение выбрасывается. Является ли мое доказательство действительным или std::stoi не гарантированно бросает это исключение при задании "abc"?

4b9b3361

Ответ 1

Выбрасывает ли std::stoi ошибку на входе "abcxyz"?

Да.

Я думаю, что ваша путаница может возникнуть из-за того, что strtol никогда не сообщает об ошибке, кроме переполнения. Он может сообщить, что никакого преобразования не было выполнено, но это никогда не упоминается как условие ошибки в стандарте C.

strtol определяется одинаково всеми тремя стандартами C, и я избавлю вас от скучных деталей, но в основном определяет "последовательность субъектов", которая является подстрокой входной строки, соответствующей фактическому числу. Следующие четыре условия эквивалентны:

  • субъектная последовательность имеет ожидаемую форму (на простом английском языке: это число)
  • последовательность объектов не пуста
  • произошло преобразование
  • *endptr != nptr (это имеет смысл только тогда, когда endptr не является нулевым)

Когда происходит переполнение, считается, что преобразование произошло.

Теперь совершенно ясно, что поскольку "abcxyz" не содержит числа, последовательность объектов строки "abcxyz" должна быть пустой, поэтому никакое преобразование не может быть выполнено. Следующая программа C90/C99/C11 подтвердит это экспериментально:

#include <stdio.h>
#include <stdlib.h>

int main() {
    char *nptr = "abcxyz", *endptr[1];
    strtol(nptr, endptr, 0);
    if (*endptr == nptr)
        printf("No conversion could be performed.\n");
    return 0;
}

Это означает, что любая соответствующая реализация std::stoi должна бросать invalid_argument при задании ввода "abcxyz" без необязательного базового аргумента.


Означает ли это, что std::stoi имеет удовлетворительную проверку ошибок?

Нет. Человек, с которым вы разговаривали, правилен, когда она говорит, что std::stoi более снисходительна, чем выполнение полной проверки errno == 0 && end != start && *end=='\0' после std::strtol, потому что std::stoi молча удаляет все символы, начиная с первого нечислового символа в строка.

На самом деле, с моей головы единственный язык, чье родное преобразование ведет себя как std::stoi, - это Javascript, и даже тогда вам нужно заставить base 10 с помощью parseInt(n, 10) избежать специального случая шестнадцатеричных чисел:

input      |  std::atoi       std::stoi      Javascript      full check 
===========+=============================================================
hello      |  0               error          error(NaN)      error      
0xygen     |  0               0              error(NaN)      error      
0x42       |  0               0              66              error      
42x0       |  42              42             42              error      
42         |  42              42             42              42         
-----------+-------------------------------------------------------------
languages  |  Perl, Ruby,     Javascript     Javascript      C#, Java,  
           |  PHP, C...       (base 10)                      Python...  

Примечание. Существуют также различия между языками в обработке пробельных и избыточных знаков +.


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

Я не знаю никакой встроенной функции, которая делает это, но boost::lexical_cast<int> будет делать то, что вы хотите. Он особенно строгий, поскольку он даже отвергает окружающие пробелы, в отличие от функции Python int(). Обратите внимание, что недопустимые символы и переполнения приводят к одному и тому же исключению, boost::bad_lexical_cast.

#include <boost/lexical_cast.hpp>

int main() {
    std::string s = "42";
    try {
        int n = boost::lexical_cast<int>(s);
        std::cout << "n = " << n << std::endl;
    } catch (boost::bad_lexical_cast) {
        std::cout << "conversion failed" << std::endl;
    }
}