Что происходит, когда я назначаю long int для int в C?
В недавнем домашнем задании мне сказали использовать long
переменная для хранения результата, так как это может быть большое число.
Я решил проверить, действительно ли это важно для меня, в моей системе (Intel Core i5/64-битный Windows 7/ GNU GCC компилятор) и обнаружил, что следующий код:
printf("sizeof(char) => %d\n", sizeof(char));
printf("sizeof(short) => %d\n", sizeof(short));
printf("sizeof(short int) => %d\n", sizeof(short int));
printf("sizeof(int) => %d\n", sizeof(int));
printf("sizeof(long) => %d\n", sizeof(long));
printf("sizeof(long int) => %d\n", sizeof(long int));
printf("sizeof(long long) => %d\n", sizeof(long long));
printf("sizeof(long long int) => %d\n", sizeof(long long int));
производит следующий вывод:
sizeof(char) => 1
sizeof(short) => 2
sizeof(short int) => 2
sizeof(int) => 4
sizeof(long) => 4
sizeof(long int) => 4
sizeof(long long) => 8
sizeof(long long int) => 8
Другими словами, в моей системе int
а также long
одинаковы, и все, что будет слишком большим для int
держать, будет слишком большим для long
держать также.
Само домашнее задание здесь не является проблемой. Интересно, как в системе, где int < long
, я должен назначить int
долго?
Я осознаю тот факт, что существует множество тесно связанных между собой вопросов по этому вопросу, но я чувствую, что ответы в них не дают мне полного понимания того, что произойдет или может произойти в процессе.
В основном я пытаюсь выяснить следующее:
- Должен ли я бросить
long
вint
до назначения, илипосколькубудет считаться вредным назначать напрямую?long
это не другой тип данных, а просто модификатор, - Что происходит в системах, где
long > int
? Будет ли результат неопределенным (или непредсказуемым) или это приведет к тому, что лишние части переменной будут опущены? - Как происходит кастинг из
long
вint
работает в C? - Как происходит назначение от
long
вint
работает в C, когда я не использую кастинг?
2 ответа
Язык гарантирует, что int
не менее 16 бит, long
не менее 32 бит, и long
может представлять как минимум все значения, которые int
может представлять.
Если вы назначите long
значение для int
объект, он будет неявно преобразован. Там нет необходимости явного приведения; это просто указывает на то же преобразование, которое произойдет в любом случае.
В вашей системе, где int
а также long
бывает одинакового размера и диапазона, преобразование тривиально; это просто копирует значение.
В системе, где long
шире чем int
, если значение не помещается в int
, тогда результат преобразования определяется реализацией. (Или, начиная с C99, он может генерировать сигнал, определяемый реализацией, но я не знаю ни одного компилятора, который на самом деле это делает.) Обычно происходит то, что старшие биты отбрасываются, но вы не должны зависеть на что. (Правила для типов без знака различны; результат преобразования целого со знаком или без знака в тип без знака хорошо определен.)
Если вам нужно безопасно назначить long
значение для int
объект, вы можете проверить, что он будет соответствовать перед выполнением назначения:
#include <limits.h> /* for INT_MIN, INT_MAX */
/* ... */
int i;
long li = /* whatever */
if (li >= INT_MIN && li <= INT_MAX) {
i = li;
}
else {
/* do something else? */
}
Детали "чего-то еще" будут зависеть от того, что вы хотите сделать.
Одна поправка: int
а также long
всегда разные типы, даже если они имеют одинаковый размер и представление. Арифметические типы свободно конвертируемы, поэтому часто это не имеет значения, но, например, int*
а также long*
являются различными и несовместимыми типами; Вы не можете назначить long*
для int*
или наоборот, без явного (и потенциально опасного) приведения.
И если вам нужно преобразовать long
значение для int
Первое, что вы должны сделать, это пересмотреть дизайн вашего кода. Иногда такие преобразования необходимы, но чаще они являются признаком того, что int
которому вы назначаете, должен был быть определен как long
на первом месте.
long
всегда может представлять все значения int
, Если имеющееся значение может быть представлено типом переменной, которую вы присваиваете, то значение сохраняется.
Если он не может быть представлен, то для типа назначения со знаком результат формально не указан, в то время как для типа назначения без знака он указывается как исходное значение по модулю 2 n, где n - количество битов в представлении значения (которое не является обязательно все биты в пункте назначения).
На практике на современных машинах вы получаете упаковку также для подписанных типов.
Это связано с тем, что современные машины используют форму дополнения до двух для представления целых чисел со знаком, без каких-либо битов, используемых для обозначения "недопустимого значения" и т. Д., Т.е. всех битов, используемых для представления значения.
С представлением значения n битов любое целочисленное значение x отображается в x + K * 2 n с целочисленной константой K, выбранной так, что результат находится в диапазоне, где половина возможных значений является отрицательной.
Так, например, с 32-битным int
значение -7 представляется как номер битового паттерна -7 + 2 32 = 2 32 -7, так что если вы отобразите число, которое битовый паттерн обозначает как целое число без знака, вы получите довольно большое число.
Причина, по которой это называется дополнением к двум, заключается в том, что это имеет смысл для двоичной системы счисления, системы счисления двух основ. Для двоичной системы счисления также есть дополнение к единице (обратите внимание на расположение апострофа). Аналогично, для десятичной системы счисления есть дополнение к десяти и дополнение к нулям. С представлением дополнения из десяти цифр десятого вы бы представляли -7 как 10000-7 = 9993. Вот и все, на самом деле.