Pergunta

Eu tenho necessidade de embalar quatro bytes assinados em 32 bits tipo integral. é isso que eu vim para:

int32_t byte(int8_t c) { return (unsigned char)c; }

int pack(char c0, char c1, ...) {
  return byte(c0) | byte(c1) << 8 | ...;
}

Esta é uma boa solução? É portátil (não no sentido comunicação)? há uma solução pronta, talvez impulso?

questão que eu estou mais preocupados é a ordem pouco ao converter de bits negativos de char para int. Eu não sei o que o comportamento correto deve ser.

Graças

Foi útil?

Solução

Eu gostava resposta de Joey Adam exceto pelo fato de que ele é escrito com macros (que causam uma dor real em muitas situações) e o compilador não vai lhe dar um aviso se 'char' não é 1 byte de largura. Esta é a minha solução (baseado fora Joey).

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);
}

omiti lançando c0-> c3 a um uint32_t como o compilador deve lidar com isso para você quando mudar e eu usei moldes c-estilo como eles vão trabalhar para qualquer C ou C ++ (o OP marcado como ambos).

Outras dicas

char não é garantido para ser assinado ou não assinado (em PowerPC Linux, padrões char para não assinado ). Espalhar a palavra!

O que você quer é algo como isto macro:

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

É feio, principalmente porque ele não joga bem com a ordem das operações da C. Além disso, a barra invertida retornos estão lá assim que esta macro não tem que ser um grande longa linha.

Além disso, a razão pela qual lançou a uint8_t antes de lançar a uint32_t é impedir extensão de sinal indesejada.

Você pode evitar moldes com conversões implícitas:

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);
}

A idéia é que você vê "converter todos os parâmetros corretamente. Shift e combiná-los", e não "para cada parâmetro, convertê-lo corretamente, mudança e combiná-lo". Não muito com ele, apesar de tudo.

Depois:

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";
}

Output:

4
5
249
-7

Este é tão portátil como os tipos uint32_t, uint8_t e int8_t. Nenhum deles é necessária em C99, e a stdint.h cabeçalho não é definido em C ++ ou C89. Se existirem os tipos e satisfazer as exigências C99, porém, o código irá funcionar. É claro que em C as funções de descompactação iria precisar de um parâmetro de função em vez de um parâmetro de modelo. Você pode preferir que em C ++ também se você quer escrever ciclos curtos para descompactar.

Para abordar o fato de que os tipos são opcionais, você poderia usar uint_least32_t, que é exigido no C99. Da mesma forma uint_least8_t e int_least8_t. Você teria que mudar o código de pack_helper e 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)));
}

Para ser honesto isso é pouco provável que seja vale a pena - as chances são o resto de sua aplicação é escrita no pressuposto de que int8_t etc existem. É uma implementação raro que não tem um bit 8 e 32 bit 2 do tipo de complemento.

"Bondade"
IMHO, esta é a melhor solução que você está indo para obter para isso. EDIT: embora eu usaria static_cast<unsigned int> vez do elenco de estilo C, e eu provavelmente não iria usar um método separado para esconder o elenco ....

Portabilidade:
Não vai haver nenhuma maneira portátil para fazer isso, porque nada diz char tem que ser oito bits, e nada diz necessidades unsigned int ser 4 bytes de largura.

Além disso, você está confiando em dados endianness e, portanto, pack'd em uma arquitetura não será utilizável em um com o endianness oposto.

há uma solução pronta, talvez aumentar?
Não que eu estou ciente.

Isto é baseado em Grant Peters e respostas Joey Adams, estendida para mostrar como a desempacotar os valores assinados (as funções de descompactação invocar as regras modulo de valores não assinados em C):

(como Steve Jessop observado nos comentários, não há necessidade de funções pack_s e pack_u separados).

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;
}

(Estes são necessários ao invés de simplesmente lançando de volta para int8_t, porque este pode causar um sinal definido pela implementação a ser levantada se o valor é superior a 127, por isso não é estritamente portátil).

Você também pode deixar o compilador fazer o trabalho para você.

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

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

Etc.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top