С++ 11 std:: stoi молча проваливается, когда база не находится в [2,36] (GCC)

Я использую GCC 4.9.0 для Linux. Здесь моя тестовая программа:

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char* argv[])
{
  size_t pos = 42;
  cout << "result: " << stoi(argv[1], &pos, atoi(argv[2])) << '\n';
  cout << "consumed: " << pos << '\n';
}

Здесь ожидаемый результат:

$ ./a.out 100 2
result: 4
consumed: 3

То есть, он проанализировал "100" в базе 2 как число 4 и использовал все 3 символа.

Мы можем делать аналогично до основания 36:

 $ ./a.out 100 36
result: 1296
consumed: 3

Но как насчет более крупных баз?

$ ./a.out 100 37
result: 0
consumed: 18446744073707449552

Что это? Предполагается, что pos является индексом, в котором он прекратил разбор. Здесь он близок к std::string::npos, но не совсем (от нескольких миллионов). И если я скомпилирую без оптимизации, тогда pos будет 18446744073703251929, поэтому он выглядит как неинициализированный мусор, несмотря на то, что я инициализировал его (до 42). И действительно, valgrind жалуется:

Conditional jump or move depends on uninitialised value(s)
  at 0x400F11: int __gnu_cxx::__stoa<long, int, char, int>(...) (in a.out)
  by 0x400EC7: std::stoi(std::string const&, unsigned long*, int) (in a.out)

Так что интересно. Кроме того, в документации std::stoi говорится, что он выбрасывает std:: invalid_argument, если преобразование не может быть выполнено. Очевидно, что в этом случае он не выполнял никакого преобразования, и он возвращал мусор в pos, и не было никакого исключения.

Аналогичные плохие вещи случаются, если base равно 1 или отрицательному.

Является ли это ошибкой в ​​реализации GCC, ошибкой в ​​стандарте или просто чем-то, чему мы должны научиться жить? Я думал, что одна из целей stoi() vs atoi() - лучшее обнаружение ошибок, но, похоже, вообще не проверяет base.


Изменить: здесь версия C той же программы, которая также печатает errno:

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

int main(int argc, char* argv[])
{
  char* pos = (char*)42;
  printf("result: %ld\n", strtol(argv[1], &pos, atoi(argv[2])));
  printf("consumed: %lu (%p)\n", pos - argv[1], pos);
  perror("errno");
  return 0;
}

Когда он работает, он делает то же самое, что и раньше. Когда это терпит неудачу, это намного более ясно:

$ ./a.out 100 37
result: 0
consumed: 18446603340345143502 (0x2a)
errno: Invalid argument

Теперь мы видим, почему pos в версии С++ было значением "мусора": это было потому, что strtol() left endptr неизменен, а оболочка С++ ошибочно вычитает начальный адрес входной строки из этого.

В версии C мы также видим, что для параметра errno установлено значение EINVAL. Документация в моей системе говорит, что это произойдет, когда base недействителен, но также говорит, что он не указан C99. Если мы напечатаем errno в версии С++, мы также можем обнаружить эту ошибку (но она не стандартная в C99 и она не указана С++ 11).

Ответ 1

[C++11: 21.5/3]: Выдает: invalid_argument, если strtol, strtoul, strtoll или strtoull сообщает, что никакое преобразование не может быть выполнено. [..]

[C99: 7.20.1.4/5]: Если последовательность объектов имеет ожидаемую форму, а значение base равно нулю, последовательность символов, начинающихся с первой цифры, интерпретируется как целочисленная константа в соответствии с правилами 6.4.4.1. Если субъектная последовательность имеет ожидаемую форму, а значение base составляет от 2 до 36, она используется как основа для преобразования, приписывая каждой букве ее значение, как указано выше. [..]

В C99 нет семантики для случая, когда base не равно нулю или между 2 и 36, поэтому результат undefined. Это не обязательно соответствует выдержке из [C++11: 21.5/3].

Короче говоря, это UB; вы ожидаете исключения только в том случае, если база действительна, но входное значение в этой базе не является необратимым. Это ошибка ни в GCC, ни в стандарте.