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

Объявление 64-битных переменных в C

У меня есть вопрос.

uint64_t var = 1; // this is 000000...00001 right?

И в моем коде это работает:

var ^ (1 << 43)

Но как он знает, 1 должен быть в 64 бит? Должен ли я писать это вместо?

var ^ ( (uint64_t) 1 << 43 )
4b9b3361

Ответ 1

Как вы и предполагали, 1 - это простой подписанный int (который, вероятно, на вашей платформе имеет ширину 32 бит в 2-х арифметических дополнениях), а также 43, поэтому, случайно 1<<43 приводит к переполнению: в фактах, если оба аргумента имеют тип int, правила оператора определяют, что результатом будет также int.

Тем не менее, в C-значении целочисленного переполнения существует поведение undefined, поэтому в принципе все может произойти. В вашем случае, возможно, компилятор испустил код для выполнения этого сдвига в 64-битном регистре, поэтому, к счастью, он работает; для получения гарантированного результата вы должны использовать вторую форму, которую вы написали, или, альтернативно, указать 1 как литерал unsigned long long с использованием суффикса ull (unsigned long long гарантированно будет не менее 64 бит).

var ^ ( 1ULL << 43 )

Ответ 2

Я рекомендую подход OP, накладываю константу ( (uint64_t) 1 << 43 )

Для небольшого примера OP, 2 ниже, скорее всего, будут работать одинаково.

uint64_t var = 1; 
// OP solution)
var ^ ( (uint64_t) 1 << 43 )
// Others suggested answer
var ^ ( 1ULL << 43 )        

Вышеуказанные результаты имеют одно значение, но разные типы. Разность потенциалов заключается в том, как существуют два типа в C: uint64_t и unsigned long long и что может следовать.

uint64_t имеет точный диапазон от 0 до 2 64 -1.
unsigned long long имеет диапазон от 0 до не менее 2 64 -1.

Если unsigned long long всегда будет 64-битным, так как кажется, что на многих машинах есть дни, нет проблемы, но пусть смотрит в будущее и говорит, что этот код запускался на машине, где unsigned long long было 16 байт (от 0 до не менее 2 128 -1).

Настроенный пример ниже: Первый результат ^ - это uint64_t, при умножении на 3 произведение будет uint64_t, выполняя по модулю 2 64 должно переполнение происходит, тогда результат присваивается d1. В следующем случае результатом ^ является unsigned long long, а при умножении на 3 произведение может быть больше 2 64 которое затем присваивается d2. Поэтому d1 и d2 имеют другой ответ.

double d1, d2;
d1 = 3*(var ^ ( (uint64_t) 1 << 43 ));
d2 = 3*(var ^ ( 1ULL << 43 ));

Если вы хотите работать с unit64_t, будьте последовательны. Не предполагайте, что unit64_t и unsigned long long совпадают. Если это нормально, чтобы ваш ответ был unsigned long long, штраф. Но, по моему опыту, если вы начинаете использовать типы фиксированного размера, такие как uint64_t, то не нужно, чтобы типы вариантов разбросали вычисления.

Ответ 3

var ^ ( 1ULL << 43 ) должен это сделать.

Ответ 4

Портативный способ иметь константу unit64_t - использовать макрос UINT64_C (от stdint.h):

UINT64_C(1) << 43

Скорее всего, UINT64_C(c) определяется чем-то вроде c ## ULL.

Из стандарта C:

Макрос INT N _C(value) должен расширяться до целочисленного постоянного выражения соответствующий типу int_least N _t. Макрос UINTN_ C (value) должен expand к целочисленному постоянному выражению, соответствующему типу uint_least N _t. Например, если uint_least64_t - это имя для типа unsigned long long int, то UINT64_C(0x123) может расширяться до целочисленная константа 0x123ULL.

Ответ 5

Ваш компилятор не знает, что сдвиг должен выполняться в 64 бит. Однако, с этой конкретной версией компилятора в этой конкретной конфигурации для этого конкретного кода, два неправильных действия делают правильное. Не рассчитывайте на это.

Предполагая, что int является 32-битным типом на вашей платформе (что очень вероятно), две ошибки в 1 << 43:

  • Если величина сдвига больше или равна ширине типа левого операнда, поведение undefined. Это означает, что если x имеет тип int или unsigned int, то x << 43 имеет поведение undefined, как и x << 32 или любое другое x << n, где n ≥ 32. Например 1u << 43 также имеют поведение undefined.
  • Если у левого операнда есть подписанный тип, а результат операции переполнения этого типа, то поведение undefined. Например, 0x12345 << 16 имеет поведение undefined, так как тип левого операнда является подписанным типом int, но значение результата не соответствует int. С другой стороны, 0x12345u << 16 хорошо определен и имеет значение 0x23450000u.

"Undefined поведение" означает, что компилятор может генерировать код, который сбой или возвращает неправильный результат. Так получилось, что в этом случае вы получили желаемый результат - это не запрещено, однако закон Мерфи требует, что в один прекрасный день сгенерированный код не будет делать то, что вы хотите.

Чтобы гарантировать, что операция выполняется в 64-битном типе, вам необходимо убедиться, что левый операнд является 64-битным типом - тип переменной, которую вы назначаете, не имеет значения. Это та же проблема, что и float x = 1 / 2, приводящая к x, содержащему 0, а не 0,5: для определения поведения арифметического оператора важны только типы операндов. Любые из (uint64)1 << 43 или (long long)1 << 43 или (unsigned long long)1 << 43 или 1ll << 43 или 1ull << 43 будут делать. Если вы используете подписанный тип, то поведение определяется только в том случае, если переполнение отсутствует, поэтому, если вы ожидаете усечения при переполнении, обязательно используйте неподписанный тип. Тип без знака обычно рекомендуется, даже если переполнение не должно происходить, потому что поведение воспроизводимо - если вы используете подписанный тип, то простой акт печати значений для целей отладки может изменить поведение (поскольку компиляторы любят использовать поведения undefined для генерации любого кода, который наиболее эффективен на микроуровне, который может быть очень чувствительным к таким вещам, как давление на распределение регистров).

Поскольку вы предполагаете, что результат имеет тип uint64_t, более ясным является выполнение всех вычислений с этим типом. Таким образом:

uint64_t var = 1;
… var ^ ((uint64_t)1 << 43) …