Создание 64-битного целого числа на основе четырех 16-битных целых чисел

Я пытаюсь использовать побитовые операторы для преобразования четырех 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].

Мне определенно нужно больше практики с побитовыми операторами (это в моем списке дел), и я уверен, что делаю здесь что-то глупое. Я просто хотел посмотреть, может ли кто-нибудь поправить меня в том, что я делаю неправильно.

Любая помощь высоко ценится и спасибо за ваше время!

Нужно ли для этого использовать побитовые операторы? Эту задачу можно легко выполнить с помощью union

Roy Avidan 17.12.2020 20:49

Кроме того, я считаю, что большинство ваших проблем связано с тем, что вам нужно привести nums[i] числа к uint64_t перед любой арифметической операцией над ними.

Roy Avidan 17.12.2020 20:53

Строить mainnum неправильно. У вас неправильные индексы и неправильные сдвиги направления. Приведите каждый элемент к 64-битному, прежде чем сдвигать его.

Weather Vane 17.12.2020 20:58

Сдвиг влево с помощью << перемещает биты из младших позиций (1, 2, 4, 8,…) в более высокие позиции. Сдвиг вправо с помощью >> перемещает биты из старших позиций в младшие. Сдвиг вправо 16-битного целого числа на 16 бит полностью отбрасывает все биты, давая ноль. Чтобы собрать четыре 16-битных целых числа в 64-битное целое, вам нужно сдвинуть три из них влево на 16, 32 и 48 бит.

Eric Postpischil 17.12.2020 20:59

При смещении приведите смещаемый операнд к желаемому типу результата. Например, nums[3] << 48 не будет работать, потому что 16-битный nums[3] будет автоматически повышаться до 32-битного int (в типичной реализации C), и сдвиг будет выполняться в этом 32-битном типе. Таким образом, его нельзя сдвинуть на 48 бит, и этот сдвиг не определен стандартом C. Вместо этого используйте (uint64_t) nums[3] << 48.

Eric Postpischil 17.12.2020 21:00

Использование операций сдвига обычно используется, когда вы хотите, чтобы ваш код был независимым от порядка байтов. Если вместо этого вы хотите другое поведение для разных порядков байтов, тогда memcpy или union — гораздо более простой вариант.

M.M 17.12.2020 21:01

Суммы смен у вас извращенные, а комментарии типа // ---- 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 бит, в зависимости от порядка следования байтов. (Есть реализации со смешанными порядками байтов, но я сомневаюсь, что вы работаете с ними.)

Eric Postpischil 17.12.2020 21:02

Всем спасибо за комментарии и прошу прощения за глупый вопрос! Я буду помнить обо всем этом, когда начну глубже копаться в побитовых операциях и т. д.

Christian Deacon 18.12.2020 03:45
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
8
1 036
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вам нужны те же/соответствующие индексы и суммы сдвига, которые вы использовали для создания 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;

Спасибо! Это прекрасно работает, и я буду помнить об этом и в будущем, когда начну углубляться в побитовые операции и т. д.

Christian Deacon 18.12.2020 03:43

Другие вопросы по теме