C/C++ упаковка подписанного символа в int
Вопрос
Мне нужно упаковать четыре подписанных байта в 32-битный целочисленный тип.вот что я придумал:
int32_t byte(int8_t c) { return (unsigned char)c; }
int pack(char c0, char c1, ...) {
return byte(c0) | byte(c1) << 8 | ...;
}
это хорошее решение?Является ли он портативным (не в смысле связи)?есть готовое решение, возможно буст?
Проблема, которая меня больше всего беспокоит, - это порядок битов при преобразовании отрицательных битов из char в int.Я не знаю, каким должно быть правильное поведение.
Спасибо
Решение
Мне понравился ответ Джои Адама, за исключением того факта, что он написан с использованием макросов (которые во многих ситуациях вызывают настоящую боль), и компилятор не выдаст вам предупреждение, если ширина «char» не превышает 1 байт.Это мое решение (на основе решения Джоуи).
inline uint32_t PACK(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) {
return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
}
inline uint32_t PACK(sint8_t c0, sint8_t c1, sint8_t c2, sint8_t c3) {
return PACK((uint8_t)c0, (uint8_t)c1, (uint8_t)c2, (uint8_t)c3);
}
Я пропустил приведение c0->c3 к uint32_t, так как компилятор должен обрабатывать это за вас при сдвиге, и я использовал приведения в стиле c, поскольку они будут работать как для c, так и для c++ (OP помечен как оба).
Другие советы
char
не гарантируется, что он подписан или не подписан (в PowerPC Linux по умолчанию char имеет значение без подписи).Распространить слово!
Вам нужно что-то вроде этого макроса:
#include <stdint.h> /* Needed for uint32_t and uint8_t */
#define PACK(c0, c1, c2, c3) \
(((uint32_t)(uint8_t)(c0) << 24) | \
((uint32_t)(uint8_t)(c1) << 16) | \
((uint32_t)(uint8_t)(c2) << 8) | \
((uint32_t)(uint8_t)(c3)))
Это некрасиво главным образом потому, что плохо сочетается с порядком операций C.Кроме того, здесь предусмотрен возврат обратной косой черты, поэтому этот макрос не обязательно должен представлять собой одну большую длинную строку.
Кроме того, причина, по которой мы приводим uint8_t перед приведением к uint32_t, заключается в предотвращении нежелательного расширения знака.
Вы можете избежать приведения с неявными преобразованиями:
uint32_t pack_helper(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) {
return c0 | (c1 << 8) | (c2 << 16) | (c3 << 24);
}
uint32_t pack(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3) {
return pack_helper(c0, c1, c2, c3);
}
Идея в том, что вы видите «правильно преобразовать все параметры.Сдвиньте и объедините их», а не «для каждого параметра правильно преобразуйте его, сдвиньте и объедините».Впрочем, в этом не так уж и много.
Затем:
template <int N>
uint8_t unpack_u(uint32_t packed) {
// cast to avoid potential warnings for implicit narrowing conversion
return static_cast<uint8_t>(packed >> (N*8));
}
template <int N>
int8_t unpack_s(uint32_t packed) {
uint8_t r = unpack_u<N>(packed);
return (r <= 127 ? r : r - 256); // thanks to caf
}
int main() {
uint32_t x = pack(4,5,6,-7);
std::cout << (int)unpack_u<0>(x) << "\n";
std::cout << (int)unpack_s<1>(x) << "\n";
std::cout << (int)unpack_u<3>(x) << "\n";
std::cout << (int)unpack_s<3>(x) << "\n";
}
Выход:
4
5
249
-7
Это так же портативно, как и uint32_t
, uint8_t
и int8_t
типы.Ни один из них не требуется в C99, а заголовок stdint.h не определен в C++ или C89.Однако если типы существуют и соответствуют требованиям C99, код будет работать.Конечно, в C функциям распаковки потребуется параметр функции вместо параметра шаблона.Вы можете предпочесть это и в C++, если хотите написать короткие циклы для распаковки.
Чтобы учесть тот факт, что типы не являются обязательными, вы можете использовать uint_least32_t
, который требуется в C99.Сходным образом uint_least8_t
и int_least8_t
.Вам придется изменить код package_helper и unpack_u:
uint_least32_t mask(uint_least32_t x) { return x & 0xFF; }
uint_least32_t pack_helper(uint_least32_t c0, uint_least32_t c1, uint_least32_t c2, uint_least32_t c3) {
return mask(c0) | (mask(c1) << 8) | (mask(c2) << 16) | (mask(c3) << 24);
}
template <int N>
uint_least8_t unpack_u(uint_least32_t packed) {
// cast to avoid potential warnings for implicit narrowing conversion
return static_cast<uint_least8_t>(mask(packed >> (N*8)));
}
Честно говоря, вряд ли это того стоит — скорее всего, остальная часть вашего приложения будет написана исходя из предположения, что int8_t
и т. д. существуют.Это редкая реализация, которая не имеет 8-битного и 32-битного типа дополнения до 2.
"Добро"
ИМХО, это лучшее решение, которое вы можете получить.РЕДАКТИРОВАТЬ:хотя я бы использовал static_cast<unsigned int>
вместо приведения в стиле C, и я бы, вероятно, не стал использовать отдельный метод для скрытия приведения....
Портативность:
Портативного способа сделать это не будет, потому что ничего не говорит char
должно быть восемь бит, и ничего не говорит unsigned int
должна иметь ширину 4 байта.
Более того, вы полагаетесь на порядок байтов, и поэтому данные, упакованные в одной архитектуре, не будут пригодны для использования в другой архитектуре.
есть готовое решение, возможно буст?
Не то, о чем я знаю.
Это основано на ответах Гранта Питерса и Джои Адамса, расширенных, чтобы показать, как распаковывать значения со знаком (функции распаковки полагаются на правила по модулю беззнаковых значений в C):
(Как отметил в комментариях Стив Джессоп, нет необходимости в отдельных pack_s
и pack_u
функции).
inline uint32_t pack(uint8_t c0, uint8_t c1, uint8_t c2, uint8_t c3)
{
return ((uint32_t)c0 << 24) | ((uint32_t)c1 << 16) |
((uint32_t)c2 << 8) | (uint32_t)c3;
}
inline uint8_t unpack_c3_u(uint32_t p)
{
return p >> 24;
}
inline uint8_t unpack_c2_u(uint32_t p)
{
return p >> 16;
}
inline uint8_t unpack_c1_u(uint32_t p)
{
return p >> 8;
}
inline uint8_t unpack_c0_u(uint32_t p)
{
return p;
}
inline uint8_t unpack_c3_s(uint32_t p)
{
int t = unpack_c3_u(p);
return t <= 127 ? t : t - 256;
}
inline uint8_t unpack_c2_s(uint32_t p)
{
int t = unpack_c2_u(p);
return t <= 127 ? t : t - 256;
}
inline uint8_t unpack_c1_s(uint32_t p)
{
int t = unpack_c1_u(p);
return t <= 127 ? t : t - 256;
}
inline uint8_t unpack_c0_s(uint32_t p)
{
int t = unpack_c0_u(p);
return t <= 127 ? t : t - 256;
}
(Это необходимо, а не просто выполнять возврат к int8_t
, поскольку последний может привести к возникновению сигнала, определенного реализацией, если значение превышает 127, поэтому он не является строго переносимым).
Вы также можете позволить компилятору сделать всю работу за вас.
union packedchars {
struct {
char v1,v2,v3,v4;
}
int data;
};
packedchars value;
value.data = 0;
value.v1 = 'a';
value.v2 = 'b;
И т. д.