Почему самый маленький 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 - говорит, что "либо результат определяется реализацией, либо генерируется определенный сигнал реализации".)