Большие отрицательные целые литералы

В Visual Studio 2010 следующая программа

#include <iostream>
using std::cout;

int main()
{
    cout << -2147483646 << '\n';
    cout << -2147483647 << '\n';
    cout << -2147483648 << '\n';    // numeric_limits<int>::min()
    cout << -2147483649 << '\n';
    cout << -2147483650 << '\n';
    cout << "..." << '\n';
    cout << -4294967293 << '\n';
    cout << -4294967294 << '\n';
    cout << -4294967295 << '\n';    // -numeric_limits<unsigned int>::max()
    cout << -4294967296 << '\n';
    cout << -4294967297 << '\n';
}

генерирует следующий вывод

-2147483646
-2147483647
2147483648
2147483647
2147483646
...
3
2
1
-4294967296
-4294967297

Что происходит?

Является ли это стандартным поведением или ошибкой Visual Studio?

Изменить: Как указывало несколько человек, нет такой вещи, как отрицательный целочисленный литерал. Более подробную информацию см. В обзоре Кит Томпсон ниже.

Ответ 1

-2147483648, например, не является целым литералом; это выражение, состоящее из унарного оператора -, примененного к литералу 2147483648.

До нового стандарта С++ 2011 С++ не требует наличия какого-либо типа размером более 32 бит (С++ 2011 добавляет long long), поэтому литерал 2147483648 не переносится.

Двойной целочисленный литерал имеет первый из следующих типов, в котором его значение соответствует:

int
long int
long long int (new in C++ 2011)

Обратите внимание, что он никогда не имеет тип unsigned в стандартном С++. В версиях стандарта C (1998) и версии 2003 (не имеющих long long int) в версиях стандарта 1998 и 2003 годов, десятичный целочисленный литерал, который слишком велик, чтобы соответствовать long int, приводит к поведению undefined. В С++ 2011, если десятичный целочисленный литерал не помещается в long long int, тогда программа "плохо сформирована".

Но gcc (по крайней мере, начиная с версии 4.6.1, последняя из которых у меня есть) не реализует семантику С++ 2011. Литерал 2147483648, который не подходит для 32-разрядной длины, рассматривается как unsigned long, по крайней мере, на моей 32-битной системе. (Это хорошо для С++ 98 или С++ 2003, поведение undefined, поэтому компилятор может делать все, что ему нравится.)

Таким образом, для типичного 32-битного типа 2's-complement int это:

cout << -2147483647 << '\n';

принимает значение int 2147483647, отрицает его и печатает результат, который соответствует ожидаемому математическому результату. Но это:

cout << -2147483648 << '\n';

(при компиляции с gcc 4.6.1) принимает значение long или unsigned long 2147483648, отрицает его как неподписанный int, уступая 2147483648, и печатает это.

Как уже упоминалось, вы можете использовать суффиксы для принудительного создания определенного типа.

Здесь небольшая программа, которую вы можете использовать, чтобы показать, как ваш компилятор обрабатывает литералы:

#include <iostream>
#include <climits>

const char *type_of(int)                { return "int"; }
const char *type_of(unsigned int)       { return "unsigned int"; }
const char *type_of(long)               { return "long"; }
const char *type_of(unsigned long)      { return "unsigned long"; }
const char *type_of(long long)          { return "long long"; }
const char *type_of(unsigned long long) { return "unsigned long long"; }

int main()
{
    std::cout << "int: " << INT_MIN << " .. " << INT_MAX << "\n";
    std::cout << "long: " << LONG_MIN << " .. " << LONG_MAX << "\n";
    std::cout << "long long: " << LLONG_MIN << " .. " << LLONG_MAX << "\n";

    std::cout << "2147483647 is of type " << type_of(2147483647) << "\n";
    std::cout << "2147483648 is of type " << type_of(2147483648) << "\n";
    std::cout << "-2147483647 is of type " << type_of(-2147483647) << "\n";
    std::cout << "-2147483648 is of type " << type_of(-2147483648) << "\n";
}

Когда я его компилирую, я получаю несколько предупреждений:

lits.cpp:18:5: warning: this decimal constant is unsigned only in ISO C90
lits.cpp:20:5: warning: this decimal constant is unsigned only in ISO C90

и следующий вывод, даже с gcc -std=c++0x:

int: -2147483648 .. 2147483647
long: -2147483648 .. 2147483647
long long: -9223372036854775808 .. 9223372036854775807
2147483647 is of type int
2147483648 is of type unsigned long
-2147483647 is of type int
-2147483648 is of type unsigned long

Я получаю тот же вывод с VS2010, по крайней мере, с настройками по умолчанию.

Ответ 2

Когда я компилирую это в GCC, я получаю следующее сообщение:

warning: this decimal constant is unsigned only in ISO C90 [enabled by default]

Это происходит для каждой строки после (и включая)

cout << -2147483648 << '\n';    // numeric_limits<int>::min()

Итак, что происходит, как компилятор Visual Studio, так и GCC позволяют нам писать эти литералы, и они просто рассматривают их так, как если бы они были помечены как unsigned. Это объясняет поведение печатаемого текста, и это делает меня довольно уверенным, что вывод правильный (предположим, что мы разместили суффикс u на числах).

Мне все же интересно, что -2147483648 не является допустимым символом целых чисел со знаком, даже если он является допустимым знаковым целочисленным значением. Любая мысль о том, что кто-нибудь?