Преобразование отрицательного числа в потоке C++
Я столкнулся с проблемой, когда C++ пытался прочитать текстовый файл, заполненный целыми числами со знаком, в шестнадцатеричной форме и проанализировать их по векторам. Я использовал поток C++ для перенаправления переменных (stream >> var), и кажется, что отрицательные числа анализируются неправильно - переменная получает значение 0, и устанавливается флаг сбоя потока.
Если я попытаюсь преобразовать строку с помощью функции strtol(), результаты будут такими, как ожидалось. Аналогичным образом, если я попытаюсь сначала перенаправить поток в целое число без знака и затем преобразовать переменную в целое число со знаком, результаты снова будут правильными, и об ошибке потока не сообщается.
Я использую gcc 6.3.0 в Debian 9.1 (x64), работающем в системе Xeon E5-2643 v3.
Кто-нибудь еще испытывал эту проблему? Я ожидал бы, что преобразование будет работать так же, как функция strtol, и не сообщать об ошибках потока. Я пропускаю некоторые настройки потока / забываю вызвать какую-то функцию или установить какой-либо флаг здесь?
Любые предложения будут ценны.
Ниже приведен пример программы на C++, демонстрирующей эту проблему.
#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
int main()
{
const char* minus_one = "0xffffffff";
std::stringstream ss;
ss << minus_one;
std::cout << "input string : " << ss.str() << "\n"; // outputs "0xffffffff"
// C-style conversion
int32_t cint;
cint = strtol(ss.str().c_str(), NULL, 0);
std::cout << "strtol conv : " << cint << " (" << std::hex << cint << ")\n"; // outputs "-1 (ffffffff)"
std::cout << std::dec;
// C++-style conversion
int32_t cppint;
ss >> std::hex >> cppint;
std::cout << std::dec << "ssextr conv : " << cppint << " (" << std::hex << cppint << ")\n"; // outputs "0 (0)" <- ERROR
std::cout << std::dec;
if (ss.fail()) std::cout << "Error converting number.\n";
// C++-style conversion with cast
uint32_t cppuint;
int32_t cppint2;
ss.clear();
ss.str(minus_one);
ss >> std::hex >> cppuint;
cppint2 = (int32_t)cppuint;
std::cout << std::dec << "ssextr cast conv: " << cppint2 << " (" << std::hex << cppint2 << ")\n"; // outputs "-1 (0xffffffff)"
std::cout << std::dec;
if (ss.fail()) std::cout << "Error converting number.\n";
exit(EXIT_SUCCESS);
}
3 ответа
int32_t cint;
cint = strtol(ss.str().c_str(), NULL, 0);
This reads the value 0xffffffff
в long
, then converts that to int32_t
, Если long
is larger than 32-bits the strtol
works and returns 0xffffffff
ie 4294967295, and converting that to int32_t
produces -1. But that's not the same as reading a negative number from the string (and if long
is 32-bits then it doesn't work as you expect, instead it returns LONG_MAX
and converts that to int32_t
, который 0x7fffffff
).
int32_t cppint;
ss >> std::hex >> cppint;
This tries to read the value 0xffffffff
в int32_t
but the value 0xffffffff
doesn't fit in that type, so reading the value fails (just like it fails with strtol
когда long
is 32-bits).
A closer equivalent to your strtol
version would be:
int32_t cppint;
long l;
if (ss >> std::hex >> l)
cppint = l;
else
// handle error ...
It's unreasonable to expect to be able to read the value 0xffffffff
into a signed 32-bit integer. strtol
and istreams do not read bit patterns, they read numbers, and the number 0xffffffff
doesn't fit in a signed 32-bit integer.
Если первый шестнадцатеричный бит f
, тогда C++ делает его "большим числом": "0x7fffffff". Кажется, что C++ не хочет выражать это как отрицательное число. Как это:
const char* minus_one = "0xf0000000"; //ssextr conv : 2147483647 (7fffffff)
std::stringstream ss;
ss << minus_one;
// C++ style conversion
int32_t cppint;
ss >> std::hex >> cppint;
std::cout << std::dec << "ssextr conv : " << cppint << " (" << std::hex << cppint << ")\n";
std::cout << std::dec;
if (ss.fail()) {
std::cout << "Error converting number.\n";
}
Проблема в том, что шестнадцатеричная запись документирована для целых чисел без знака. strtol
является функцией C и, по-видимому, является более терпимым к шестнадцатеричному представлению отрицательного целого числа, и внутренне считывает строку как значение без знака, а затем повторно интерпретирует ее как значение со знаком. Но даже в C такая обработка не указана для strtol
и для преобразования значения без знака, которое не может быть представлено в типе со знаком, либо результат определяется реализацией, либо генерируется сигнал, определяемый реализацией. (из черновика 1570 для C11 6.3.1.3 [Преобразования] Целые числа со знаком и без знака)
Вполне вероятно, что это работает таким образом, чтобы не ломать тонны унаследованного кода, но C++ является более новым языком, и разработчики решили быть более строгими для шестнадцатеричного представления.