Почему самый маленький int, -2147483648, имеет тип "long"?

Для школьного проекта я должен закодировать функцию C printf. Все идет хорошо, но есть один вопрос, на который я не могу найти хороший ответ, так что я здесь.

printf("PRINTF(d) \t: %d\n", -2147483648);

говорит мне (gcc -Werror -Wextra -Wall):

   error: format specifies type 'int' but the argument has type 'long'
      [-Werror,-Wformat]
        printf("PRINTF(d) \t: %d\n", -2147483648);
                              ~~     ^~~~~~~~~~~
                              %ld

Но если я использую переменную int, все идет хорошо:

int i;

i = -2147483648;
printf("%d", i);

Почему?

EDIT:

Я понял много моментов, и они были очень интересными. Во всяком случае, я думаю, printf использует <stdarg.h> librairy, и поэтому va_arg(va_list ap, type) также должен возвращать правильный тип. Для %d и %i, очевидно, возвращаемый тип является int. Что-то меняет?

Ответ 1

В C, -2147483648 не является целочисленной константой. 2147483648 - целочисленная константа, а - - только унарный оператор, применяемый к ней, что дает постоянное выражение. Значение 2147483648 не подходит для int (оно слишком велико, 2147483647 обычно является наибольшим целым числом), и поэтому целочисленная константа имеет тип long, что вызывает проблему, которую вы наблюдаете. Если вы хотите упомянуть нижний предел для int, используйте макрос INT_MIN из <limits.h> (переносной подход) или избегайте упоминания 2147483648:

printf("PRINTF(d) \t: %d\n", -1 - 2147483647);

Ответ 2

Проблема заключается в том, что -2147483648 не является целым литералом. Это выражение, состоящее из унарного оператора отрицания - и целого числа 2147483648, которое слишком велико, чтобы быть int, если int - 32 бита. Поскольку компилятор выберет подходящее целое число со знаком для представления 2147483648 до применения оператора отрицания, тип результата будет больше, чем int.

Если вы знаете, что ваш int - 32 бита, и вы хотите избежать предупреждения без искажения читаемости, используйте явное выражение:

printf("PRINTF(d) \t: %d\n", (int)(-2147483648));

Это определенное поведение на машине с двумя дополнениями с 32-разрядным int s.

Чтобы увеличить теоретическую переносимость, используйте INT_MIN вместо номера, и сообщите нам, где вы нашли не-2-порционную машину, чтобы проверить ее.


Чтобы быть ясным, последний абзац был отчасти шуткой. INT_MIN определенно подходит, если вы имеете в виду "наименьший int", потому что int изменяется по размеру. Например, есть еще много 16-битных реализаций. Выделение -2 31 полезно только в том случае, если вы определенно всегда имеете в виду именно это значение, и в этом случае вы, вероятно, используете тип фиксированного размера, например int32_t, а не int.

Возможно, вам понадобится альтернатива для записи числа в десятичном формате, чтобы сделать его более понятным для тех, кто может не заметить разницу между 2147483648 и 2174483648, но вам нужно быть осторожным.

Как упоминалось выше, на 32-битной машине с 2-мя дополнительными компонентами (int)(-2147483648) не будет переполняться и поэтому хорошо определен, поскольку -2147483648 будет обрабатываться как более широкий тип подписей. Однако для (int)(-0x80000000) это неверно. 0x80000000 будет рассматриваться как unsigned int (так как он вписывается в неподписанное представление); -0x80000000 четко определен (но - не имеет никакого эффекта, если int - 32 бита), а преобразование полученного unsigned int 0x80000000 в int связано с переполнением. Чтобы избежать переполнения, вам нужно будет указать шестнадцатеричную константу на подписанный тип: (int)(-(long long)(0x80000000)).

Аналогичным образом, вам нужно позаботиться, если вы хотите использовать левый оператор сдвига. 1<<31 - поведение undefined на 32-битных машинах с 32-разрядным (или меньшим) int s; он будет оценивать только 2 31 если int составляет не менее 33 бит, поскольку сдвиг влево по битам k только четко определен, если k строго меньше числа не- знаковые биты целочисленного типа левого аргумента.

1LL<<31 является безопасным, так как long long int требуется для представления 2 63 -1, поэтому его размер бит должен быть больше 32. Таким образом, форма

(int)(-(1LL<<31))

возможно, является наиболее читаемым. YMMV.


Для любых передающих педантов этот вопрос помечен C, а последний C-проект (n1570.pdf) говорит о E1 << E2, где E1 имеет подписанный тип, что значение определено только в том случае, если E1 является неотрицательным и E1 × 2E2 "представляется в типе результата". (раздел 6.5.7, пункт 4).

Это отличается от С++, в котором приложение оператора сдвига слева определено, если E1 неотрицательно и E1 × 2E2 "является представимым в соответствующем неподписанном типе типа результата "(раздел 5.8, пункт 2, добавлен курсор).

В С++, согласно самому последнему проекту стандарта, преобразование целочисленного значения в целочисленный тип со знаком определяется реализацией, если значение не может быть представлено в целевом типе (раздел 4,7, пункт 3). Соответствующий пункт стандарта С - и раздел 6.3.1.3, пункт. 3 - говорит, что "либо результат определяется реализацией, либо генерируется определенный сигнал реализации".)