C / C ++ упаковывает подписанный символ в int - PullRequest
4 голосов
/ 13 марта 2010

Мне нужно упаковать четыре байта со знаком в 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. Я не знаю, каким должно быть правильное поведение.

Спасибо

Ответы [ 6 ]

7 голосов
/ 13 марта 2010

Мне понравился ответ Джои Адама, за исключением того факта, что он написан с помощью макросов (которые во многих ситуациях вызывают настоящую боль), и компилятор не выдаст вам предупреждение, если '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 помечен как оба). 1004 *

7 голосов
/ 13 марта 2010

char гарантированно не будет подписано или не подписано (в PowerPC Linux char по умолчанию равно unsigned ). Распространите слово!

То, что вы хотите, это что-то вроде этого макроса:

#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)))

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

Кроме того, причина, по которой мы приводим к uint8_t перед приведением к uint32_t, заключается в предотвращении нежелательного расширения знака.

3 голосов
/ 13 марта 2010

Вы можете избежать приведений с неявными преобразованиями:

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. Вам придется изменить код pack_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.

1 голос
/ 13 марта 2010

Это основано на ответах Гранта Петерса и Джои Адамса, расширенных, чтобы показать, как распаковать подписанные значения (функции распаковки полагаются на правила по модулю беззнаковых значений в 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, поэтому он не является строго переносимым).

1 голос
/ 13 марта 2010

"Совершенство"
ИМХО, это лучшее решение, которое вы получите для этого. РЕДАКТИРОВАТЬ: хотя я бы использовал static_cast<unsigned int> вместо броска в стиле C, и я бы, вероятно, не использовал отдельный метод, чтобы скрыть приведение ....

Портативность:
Не будет никакого портативного способа сделать это, потому что ничто не говорит, что char должен быть восьмибитным, и ничто не говорит, что unsigned int должен быть 4 байта в ширину.

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

есть ли готовое решение, возможно, повышение?
Не знаю, о чем я знаю.

0 голосов
/ 13 марта 2010

Вы также можете позволить компилятору сделать всю работу за вас.

union packedchars {
  struct {
    char v1,v2,v3,v4;
  }
  int data;
};

packedchars value;
value.data = 0;
value.v1 = 'a';
value.v2 = 'b;

И т.д.

...