C++ упаковывает разные типы данных в одну длинную переменную

Я попал в ловушку, пытаясь упаковать пару переменных в одну переменную длиной 8 байт.

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

Поэтому я сделал следующее:

typedef unsigned long long PACKAGE; // 8 byte (shows as _int64 in debug)
(sizeof returns '8')
unsigned int dat1   = 25;  // 1 byte long max
unsigned int dat2   = 1;   // 4 bit long max
unsigned int dat3   = 100; // 2 byte long max
unsigned int dat4   = 200; // 4 byte long max
unsigned int dat5   = 2;   // 4 bit long max

Затем я создаю переменную типа PACKAGE, которая пуста (0)

PACKAGE pack = 0;

И я хочу добавить переменные в этот пакет, используя бинарные операции, я делаю:

pack = (dat1 << 56) | (dat2 << 52) | (dat3 << 36) | (dat4 << 4) | dat5;

это работает только наполовину, я посчитал, что я должен получить десятичное значение пакета, равное 2526526262902525058, или

0010001100010000000001100100000000000000000000000000110010000010

как двоичный, однако вместо этого я получаю 588254914 или 00100011000100000000111011000010‬ как двоичный файл, который каким-то образом правильный в хвосте и голове, но где-то отсутствует средняя часть.

И когда это будет сделано, я все еще собираюсь каким-то образом извлечь данные обратно.


person jinxed    schedule 03.08.2016    source источник
comment
В чем именно заключается ваш вопрос?   -  person Robᵩ    schedule 03.08.2016
comment
Возможно, вы могли бы использовать объединение с uint64_t и структурой битового поля? Хотя подобная каламбуризация нарушает строгое правило алиасинга.   -  person Some programmer dude    schedule 03.08.2016
comment
Переменная int, сдвинутая на 56 позиций, неявно сдвигает ее только на 24 позиции, что объясняет, почему вы получаете неправильный ответ. Это предполагает, что int составляет 4 байта...   -  person MS Srikkanth    schedule 03.08.2016
comment
@Robᵩ, ну, мой вопрос: «Что я делаю неправильно?» и «как сделать это правильно?». Честно говоря, я не очень разбираюсь в бинарных операциях, так как раньше использовал стандартные типы, поэтому мог просто упустить что-то очевидное. Но веб пока не очень помогает по теме.   -  person jinxed    schedule 03.08.2016
comment
@jinxed Вместо этого используйте структуру битового поля.   -  person πάντα ῥεῖ    schedule 03.08.2016
comment
@Madhusudhan, ах, возможно, проблема в этом. Обычно мне нужна информация только из первого байта из 4 байтов int. Кроме dat4 и 3 - от которых мне нужно 4 и 2 байта соответственно. Может быть, мне нужна какая-то промежуточная переменная размером 1 байт в качестве посредника?   -  person jinxed    schedule 03.08.2016
comment
int странный зверь. В зависимости от компилятора он может иметь длину от 16 бит до, по крайней мере, 64 бит. Это делает его случайное переполнение относительно легким. Если ваш компилятор поддерживает их, попробуйте использовать целые числа фиксированной ширины.   -  person user4581301    schedule 03.08.2016
comment
dat1 << 56, но dat1 был объявлен как unsigned int. Если unsigned int на вашей платформе 32-битная, то это поведение undefined - если 64-битная, то все в порядке.   -  person Jesper Juhl    schedule 03.08.2016
comment
Да благословит Бог этот сайт и всех вас :)   -  person jinxed    schedule 03.08.2016
comment
@jinxed не стесняйтесь хвалить, но пожалуйста не привносите в это воображаемых существ, таких как боги. Да, я действительно нахожу это оскорбительным.   -  person Jesper Juhl    schedule 03.08.2016
comment
@JesperJuhl Я тоже, так что не стесняйтесь воспринимать это как обобщенную, популярную фразу, означающую «спасибо, наилучшие пожелания».   -  person jinxed    schedule 03.08.2016
comment
Ты мог бы тогда просто написать это ;)   -  person Jesper Juhl    schedule 03.08.2016


Ответы (2)


Я бы предпочел использовать структуру bitfield для представления такого типа (также используйте uint64_t чтобы быть уверенным в доступном размере):

union PACKAGE {
    struct bits {
        uint64_t dat1 : 8;  // 1 byte long max
        uint64_t dat2 : 4;  // 4 bit long max
        uint64_t dat3 : 16; // 2 byte long max
        uint64_t dat4 : 32; // 4 byte long max
        uint64_t dat5 : 4;  // 4 bit long max
    };
    uint64_t whole; // for convenience
};

Как упоминалось в комментариях вы даже можете использовать тип данных uint_least64_t, чтобы ваша цель поддерживала это (поскольку доступность uint64_t не является обязательной для текущего стандарта С++):

union PACKAGE {
    struct bits {
        uint_least64_t dat1 : 8;  // 1 byte long max
        uint_least64_t dat2 : 4;  // 4 bit long max
        uint_least64_t dat3 : 16; // 2 byte long max
        uint_least64_t dat4 : 32; // 4 byte long max
        uint_least64_t dat5 : 4;  // 4 bit long max
    };
    uint_least64_t whole; // for convenience
};
person πάντα ῥεῖ    schedule 03.08.2016
comment
Будущие читатели: обратите внимание на использование uint64_t. Этот тип, если он поддерживается вашим компилятором, гарантирует беззнаковое 64-битное целое число. Определение можно найти в <cstdint> - person user4581301; 03.08.2016
comment
@user4581301 user4581301 Это действительно поддерживается стандартом. - person πάντα ῥεῖ; 03.08.2016
comment
Спасибо, я мог бы просто пойти на это на самом деле - просто нужно потратить некоторое время на чтение материала по этому вопросу, раньше я не пересекался с битовыми полями. - person jinxed; 03.08.2016
comment
@jinxed Де нада. Вы можете изменить свой accept, так как это должно быть каноническим способом для обработки таких структур. - person πάντα ῥεῖ; 03.08.2016
comment
@ πάνταῥεῖ На самом деле это необязательно по стандарту. - person rubenvb; 03.08.2016
comment
@rubenvb Образцы любых современных реализаций компилятора, которые не обеспечивают uint64_t? - person πάντα ῥεῖ; 03.08.2016
comment
Компилятор будет довольно громким, если uint64_t отсутствует, так что вы узнаете заранее, если не сделаете этого заранее. Скорее всего, его не будет только в компиляторах для облегченных встраиваемых систем. - person user4581301; 03.08.2016
comment
С другой стороны, uint_least64_t не является обязательным, если вы можете позволить себе немного расточительства. Почему это обязательно, а uint64_t, вероятно, не имеет за собой действительно интересной истории. - person user4581301; 03.08.2016
comment
@πάνταῥεῖ Это не имеет значения. Если вы говорите, что он поддерживается (под которым вы, вероятно, подразумеваете, что он включен и полностью определен) Стандартом, вы ошибаетесь. Подобные предположения делают код не таким уж переносимым через 10-20 лет. Пахнет точно так же, как долгий 64-битный фиаско в кросс-платформенном мире. - person rubenvb; 03.08.2016
comment
@rubenvb Если вы говорите, что он поддерживается (что вы, вероятно, имеете в виду, включено и полностью определено) Стандартом, вы ошибаетесь. Где я это сделал в своем ответе? - person πάντα ῥεῖ; 04.08.2016

Предполагая, что sizeof(unsigned int) != sizeof(unsigned long long), левый операнд каждого сдвига имеет неправильный тип. Каждая операция сдвига усекается (вероятно, до 32 бит).

Попробуйте, например:

typedef unsigned long long PACKAGE; // 8 byte (shows as _int64 in debug)
(sizeof returns '8')
unsigned long long dat1   = 25;  // 1 byte long max
unsigned long long dat2   = 1;   // 4 bit long max
unsigned long long dat3   = 100; // 2 byte long max
unsigned long long dat4   = 200; // 4 byte long max
unsigned long long dat5   = 2;   // 4 bit long max

pack = (dat1 << 56) | (dat2 << 52) | (dat3 << 36) | (dat4 << 4) | dat5;

or:

typedef unsigned long long PACKAGE; // 8 byte (shows as _int64 in debug)
(sizeof returns '8')
unsigned int dat1   = 25;  // 1 byte long max
unsigned int dat2   = 1;   // 4 bit long max
unsigned int dat3   = 100; // 2 byte long max
unsigned int dat4   = 200; // 4 byte long max
unsigned int dat5   = 2;   // 4 bit long max

pack = ((PACKAGE)dat1 << 56) | ((PACKAGE)dat2 << 52) | ((PACKAGE)dat3 << 36) | ((PACKAGE)dat4 << 4) | (PACKAGE)dat5;

Примечание. Хорошо, на самом деле каждая операция сдвига, в которой правый операнд больше, чем размер левого типа в битах, вызывает неопределенное поведение. Типичным неопределенным поведением является усечение, но любое другое поведение, вплоть до глобальной термоядерной войны, разрешено стандартом.

person Robᵩ    schedule 03.08.2016
comment
Я позволю кому-нибудь еще ответить о том, как использовать битовые поля. - person Robᵩ; 03.08.2016
comment
Да, sizeof(unsigned int) != sizeof(unsigned long long) - дайте мне время попробовать, спасибо - person jinxed; 03.08.2016
comment
Оба способа работают, так что может быть проблема компиляции в 32-битном режиме, что приводит к неопределенному поведению? Эх, удобно. Я мог бы лучше попытаться посмотреть в другую сторону, может быть, с фиксированной шириной целых чисел или битовых полей, как люди предложили выше... - person jinxed; 03.08.2016