Подтвердить что ты не робот

Каков надлежащий способ хранения более узких типов данных в более широкий тип данных на языке 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;
4b9b3361

Ответ 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 добавляется к отрицательному числу, что приводит к очень большому значению без знака.