Каков надлежащий способ хранения более узких типов данных в более широкий тип данных на языке C?
В настоящее время я исправляю устаревшую ошибку в коде C. В процессе исправления этой ошибки я сохранил unsigned int
в unsigned long long
. Но, к моему удивлению, математика перестала работать, когда я скомпилировал этот код в 64-битной версии GCC
. Я обнаружил, что проблема в том, что когда я назначил значение long long
int, тогда я получил число, которое выглядело как 0x0000000012345678
, но на 64-битной машине это число стало 0xFFFFFFFF12345678
.
Может кто-нибудь объяснить мне или указать мне какую-то спецификацию или документацию о том, что должно произойти при хранении меньшего типа данных в более крупном, и, возможно, какой подходящий шаблон для этого делается на C?
Обновление - пример кода
Вот что я делаю:
// Results in 0xFFFFFFFFC0000000 in 64 bit gcc 4.1.2
// Results in 0x00000000C0000000 in 32 bit gcc 3.4.6
u_long foo = 3 * 1024 * 1024 * 1024;
Ответы
Ответ 1
Я думаю, вы должны сказать компилятору, что число справа не указано. В противном случае он думает, что это нормальный подписанный int, и поскольку бит знака установлен, он считает его отрицательным, а затем он подписывает - расширяет его в приемник.
Итак, сделайте некоторое беззнаковое кастинг справа.
Ответ 2
Выражения, как правило, оцениваются независимо; на их результаты не влияет контекст, в котором они появляются.
Целочисленная константа, такая как 1024
, имеет наименьшую из int
, long int
, long long int
, в которую будет вписываться ее значение; в частном случае 1024
, что всегда int
.
Я предполагаю, что u_long
является typedef для unsigned long
(хотя вы также упоминали long long
в своем вопросе).
Итак, дано:
unsigned long foo = 3 * 1024 * 1024 * 1024;
4 константы в выражении инициализации все типа int
, и все три умножения int
-by- int
. Результат оказывается больше (в 1,5 раза), чем 2 31 что означает, что он не будет вписываться в int
в систему, где int
- 32 бита. Результат int
, независимо от того, что он есть, будет неявно преобразован в целевой тип unsigned long
, но к этому времени он слишком поздно; переполнение уже произошло.
Переполнение означает, что ваш код имеет поведение undefined (и поскольку это можно определить во время компиляции, я бы ожидал, что ваш компилятор предупредит об этом). На практике подписанное переполнение обычно обертывается, поэтому указанное выше правило обычно устанавливает foo
в -1073741824
. Вы не можете рассчитывать на это (и это не то, что вы хотите в любом случае).
Идеальное решение заключается в том, чтобы избежать неявных преобразований, гарантируя, что все имеет целевой тип в первую очередь:
unsigned long foo = 3UL * 1024UL * 1024UL * 1024UL;
(Строго говоря, только первый операнд должен иметь тип unsigned long
, но проще быть последовательным.)
Посмотрим на более общий случай:
int a, b, c, d; /* assume these are initialized */
unsigned long foo = a * b * c * d;
Вы не можете добавить суффикс UL
к переменной. Если возможно, вы должны изменить объявления a
, b
, c
и d
, так что они имеют тип unsigned long long
, но, возможно, есть и другая причина, по которой они должны быть типа int
, Вы можете добавить броски, чтобы явно преобразовать каждый из них в правильный тип. Используя трансляции, вы можете точно контролировать, когда выполняются преобразования:
unsigned long foo = (unsigned long)a *
(unsigned long)b *
(unsigned long)d *
(unsigned long)d;
Это становится немного подробным; вы можете рассмотреть применение применения только к самому левому операнду (после того, как вы поймете, как выражение анализируется).
ПРИМЕЧАНИЕ. Это не будет работать:
unsigned long foo = (unsigned long)(a * b * c * d);
Листинг преобразует результат int
в unsigned long
, но только после того, как переполнение уже произошло. Он просто указывает явно листинг, который был бы выполнен неявно.
Ответ 3
Интегральные литералы с суффиксом являются int, если они могут поместиться, в вашем случае 3
и 1024
могут определенно соответствовать. Это описано в стандартном разделе проекта C99 6.4.4.1
Целочисленные константы, цитата этого раздела может быть найдена в моем ответе на Являются ли макросы C неявным образом?. p >
Далее у нас есть умножение, которое выполняет обычные преобразования арифметических преобразований на его операндах, но так как они все int, результат которых слишком велик, чтобы соответствовать подписанному int, что приводит к переполнению. Это поведение undefined в соответствии с разделом 5, в котором говорится:
Если при оценке выражения возникает исключительное условие (т.е. если результат не определяется математически или нет в диапазоне представимых значений для его тип), поведение undefined.
Мы можем обнаружить это поведение undefined эмпирически, используя флаги clang и -fsanitize=undefined
(увидеть его вживую), в котором говорится:
Ошибка выполнения: целочисленное переполнение цепочки: 3145728 * 1024 не может быть представлено в типе 'int'
Хотя в двух дополнениях это будет просто отрицательным числом. Один из способов исправить это - использовать суффикс ul
:
3ul * 1024ul * 1024ul * 1024ul
Итак, почему отрицательное число, преобразованное в значение без знака, дает очень большое значение без знака? Это описано в разделе 6.3.1.3
Целочисленные и беззнаковые целые числа, которые гласят:
В противном случае, если новый тип без знака, значение преобразуется путем многократного добавления или вычитая одно больше максимального значения, которое может быть представлено в новом типе пока значение не окажется в диапазоне нового типа .49)
который в основном означает unsigned long max + 1
добавляется к отрицательному числу, что приводит к очень большому значению без знака.