Я использую 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).