Я пытаюсь использовать побитовые операторы для преобразования четырех 16-битных целых чисел в одно 64-битное целое число в C (ОС - это Linux, работающий под управлением Ubuntu 20.04 Server). Моя общая цель состоит в том, чтобы иметь возможность сделать это с четырьмя 32-битными целыми числами в одно 128-битное целое число (исходный IP-адрес IPv6 из четырех 32-битных целых чисел внутри структуры in6_addr
). Однако в моем примере ниже я использую четыре 16-битных целых числа и одно 64-битное целое число, поскольку у меня были проблемы с назначением 128-битных целых чисел с помощью GCC (я предполагаю, что могу применить ту же логику, что и ниже, к моему Общая цель).
Порядок байтов в моей системе в этом случае тоже с прямым порядком байтов. Однако, если вы хотите показать, как это сделать и с обратным порядком байтов, не стесняйтесь!
Вот мой текущий тестовый код на C:
#include <stdio.h>
#include <inttypes.h>
int main()
{
uint16_t nums[4];
uint64_t num = 2310051230312123321;
uint64_t mainnum = 0;
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
nums[0] = num >> 0; // xxxx ---- ---- ----
nums[1] = num >> 16; // ---- ---- ---- xxxx
nums[2] = num >> 32; // ---- ---- xxxx ----
nums[3] = num >> 48; // ---- xxxx ---- ----
#else
/*
nums[0] = num << 0;
nums[1] = num << 16;
nums[2] = num << 32;
nums[3] = num << 48;
*/
#endif
printf("Num => %" PRIu64 "\nNum #1 => %" PRIu16 "\nNum #2 => %" PRIu16 "\nNum #3 => %" PRIu16 "\nNum #4 => %" PRIu16 "\n\n", num, nums[0], nums[1], nums[2], nums[3]);
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
mainnum = ((uint64_t)(nums[0] >> 16 | nums[1]) >> 48) | (nums[3] << 16 | nums[2]);
#else
/* ... */
#endif
uint16_t other[4];
other[0] = mainnum >> 0;
other[1] = mainnum >> 16;
other[2] = mainnum >> 32;
other[3] = mainnum >> 48;
printf("Num => %" PRIu64 "\nNum #1 => %" PRIu16 "\nNum #2 => %" PRIu16 "\nNum #3 => %" PRIu16 "\nNum #4 => %" PRIu16 "\n", mainnum, other[0], other[1], other[2], other[3]);
return 0;
}
Программа выводит следующее:
Num => 2310051230312123321
Num #1 => 19385
Num #2 => 54197
Num #3 => 62298
Num #4 => 8206
Num => 537850714
Num #1 => 62298
Num #2 => 8206
Num #3 => 0
Num #4 => 0
Я хочу, чтобы num
и mainnum
были одинаковыми.
Я знаю, что я чего-то здесь не понимаю, и у меня нет большого опыта работы с побитовыми операторами.
mainnum = ((uint64_t)(nums[0] >> 16 | nums[1]) >> 48) | (nums[3] << 16 | nums[2]);
Насколько я понимаю, я начинаю с nums[0]
, и я предполагаю, что это должно заполнить первые 16 бит этим значением. Отсюда я сдвигаю вправо на 16 бит, чтобы поместить нас в 48-битное смещение, и заменяю 16 бит после этого на nums[1]
(в котором я считаю, что это наиболее значимо для прямого порядка байтов).
Отсюда я затем сдвигаю mainnum
вправо на 48 бит, чтобы получить 16-битное смещение. Затем я заменяю следующие 16 бит на nums[3]
, сдвигаю влево на 16 бит и заменяю остальные на nums[2]
.
Мне определенно нужно больше практики с побитовыми операторами (это в моем списке дел), и я уверен, что делаю здесь что-то глупое. Я просто хотел посмотреть, может ли кто-нибудь поправить меня в том, что я делаю неправильно.
Любая помощь высоко ценится и спасибо за ваше время!
Кроме того, я считаю, что большинство ваших проблем связано с тем, что вам нужно привести nums[i]
числа к uint64_t
перед любой арифметической операцией над ними.
Строить mainnum
неправильно. У вас неправильные индексы и неправильные сдвиги направления. Приведите каждый элемент к 64-битному, прежде чем сдвигать его.
Сдвиг влево с помощью <<
перемещает биты из младших позиций (1, 2, 4, 8,…) в более высокие позиции. Сдвиг вправо с помощью >>
перемещает биты из старших позиций в младшие. Сдвиг вправо 16-битного целого числа на 16 бит полностью отбрасывает все биты, давая ноль. Чтобы собрать четыре 16-битных целых числа в 64-битное целое, вам нужно сдвинуть три из них влево на 16, 32 и 48 бит.
При смещении приведите смещаемый операнд к желаемому типу результата. Например, nums[3] << 48
не будет работать, потому что 16-битный nums[3]
будет автоматически повышаться до 32-битного int
(в типичной реализации C), и сдвиг будет выполняться в этом 32-битном типе. Таким образом, его нельзя сдвинуть на 48 бит, и этот сдвиг не определен стандартом C. Вместо этого используйте (uint64_t) nums[3] << 48
.
Использование операций сдвига обычно используется, когда вы хотите, чтобы ваш код был независимым от порядка байтов. Если вместо этого вы хотите другое поведение для разных порядков байтов, тогда memcpy
или union
— гораздо более простой вариант.
Суммы смен у вас извращенные, а комментарии типа // ---- xxxx ---- ----
не соответствуют суммам. Вы либо сдвинете слово 0 на 0*16 бит, слово 1 на 1*16 бит, слово 2 на 2*16 бит и слово 3 на 3*16 бит, либо слово 0 на 3*16 бит, слово 1 на 2*16 бит, слово 2 на 1*16 бит и слово 3 на 0*16 бит, в зависимости от порядка следования байтов. (Есть реализации со смешанными порядками байтов, но я сомневаюсь, что вы работаете с ними.)
Всем спасибо за комментарии и прошу прощения за глупый вопрос! Я буду помнить обо всем этом, когда начну глубже копаться в побитовых операциях и т. д.
Вам нужны те же/соответствующие индексы и суммы сдвига, которые вы использовали для создания nums
из num
:
nums[0] = num >> 0; // xxxx ---- ---- ----
nums[1] = num >> 16; // ---- ---- ---- xxxx
nums[2] = num >> 32; // ---- ---- xxxx ----
nums[3] = num >> 48; // ---- xxxx ---- ----
И каждому термину требуется приведение (uint64_t)
, чтобы принудительно повысить его до 64 бит. В противном случае сдвиг превысит размер, используемый для промежуточных терминов в правой части задания (например, они будут выполнены с помощью int
и/или unsigned int
).
mainnum |= (uint64_t) nums[0] << 0;
mainnum |= (uint64_t) nums[1] << 16;
mainnum |= (uint64_t) nums[2] << 32;
mainnum |= (uint64_t) nums[3] << 48;
Я предпочитаю приведенное выше, потому что оно чище/понятнее и [при оптимизации] будет создавать тот же код, что и с одним оператором:
mainnum =
(uint64_t) nums[0] << 0 |
(uint64_t) nums[1] << 16 |
(uint64_t) nums[2] << 32 |
(uint64_t) nums[3] << 48;
Спасибо! Это прекрасно работает, и я буду помнить об этом и в будущем, когда начну углубляться в побитовые операции и т. д.
Нужно ли для этого использовать побитовые операторы? Эту задачу можно легко выполнить с помощью
union