2010-03-13 7 views
4

J'ai besoin d'empaqueter quatre octets signés dans le type intégral de 32 bits. ce que je suis venu à:C/C++ emballage signé char dans int

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

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

est-ce une bonne solution? Est-ce portable (pas dans le sens de la communication)? est-il une solution prête à l'emploi, peut-être booster?

Je suis surtout préoccupé par l'ordre des bits lors de la conversion de bits négatifs de char en int. Je ne sais pas quel devrait être le bon comportement.

Merci

Répondre

7

J'ai aimé la réponse de Joey Adam, sauf qu'elle est écrite avec des macros (qui causent une vraie douleur dans de nombreuses situations) et le compilateur ne vous avertira pas si 'char' n'a pas 1 octet. C'est ma solution (basée sur celle de 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); 
} 

J'ai omis casting c0-> c3 à un uint32_t que le compilateur doit gérer pour vous lors du passage et moi c style transtypages ils vont travailler pour c ou C++ (OP Marqué tous les deux).

+5

En C et C++, un 'char' _always_ a une taille d'un octet. Un octet peut ne pas être huit bits. –

+0

+1: Pour diviser en deux fonctions et utiliser la fonction au lieu de la macro. C'est un cas idéal pour avoir une fonction en ligne. – Arun

+0

Je suis curieux de savoir pourquoi vous adoptez une fonction C++ (fonctions inline) mais en condamne une autre (opérateurs de cast améliorés) .... Les conversions de style C sont déconseillées pour une raison. Sinon +1. –

1

"bonté"
à mon humble avis, c'est la meilleure solution que vous allez obtenir pour cela. EDIT: si j'utiliser static_cast<unsigned int> au lieu de la distribution de style C, et je serais probablement pas utiliser une méthode distincte pour cacher la distribution ....

Portabilité:
Il va être pas manière portable de le faire parce que rien ne dit char doit être huit bits, et rien ne dit unsigned int doit être de 4 octets de large.

En outre, vous vous fiez à l'endianness et donc les données pack'd sur une architecture ne seront pas utilisables sur un avec l'endianness opposé.

Y at-il une solution prête à l'emploi, peut-être booster?
Non dont je suis au courant.

+1

Dans les deux C et C++, un 'char' est assuré d'avoir une taille d'un octet. –

+0

@ James McNellis: Non, ce n'est pas le cas. Voir ici -> http://stackoverflow.com/questions/881894/is-char-guaranteed-to-be-exactly-8-bit-long-in-c –

+0

Endianness de côté, en supposant que les caractères sont en 8 bits et que les entiers non signés sont 32 bits, je pense que l'astuce de l'union est bien même quand les règles strictes d'aliasing sont considérées (puisque char peut alias n'importe quoi). J'ai peut-être tort. –

7

char n'est pas garanti d'être signé ou non signé (sur PowerPC Linux, le caractère par défaut est non signé). Passez le mot!

Ce que vous voulez est quelque chose comme cette 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))) 

Il est laid, principalement parce qu'il ne joue pas bien avec l'ordre de C des opérations. De plus, les backslash-returns sont là donc cette macro n'a pas besoin d'être une grande ligne longue.

De même, la raison pour laquelle nous avons lancé uint8_t avant d'utiliser uint32_t est d'empêcher l'extension de signe indésirable.

+0

Pourquoi emballes-tu c1 3 fois? Vous pouvez vous occuper du problème de l'ordre des opérations en mettant les arguments de la macro entre parenthèses .... –

+0

Ahh - Je vois que vous l'avez corrigé. +1 alors. –

+0

Oups. Je l'ai réparé. –

1

Ceci est basé sur les réponses Grant Peters et Joey Adams, étendu pour montrer comment déballer les valeurs signées (les fonctions Déballez reposent sur les règles modulo des valeurs non signées en C):

(Comme Steve Jessop a noté dans les commentaires, il n'y a pas besoin de fonctions séparées pack_s et 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; 
} 

(Ces derniers sont nécessaires plutôt que de simplement jeter retour à int8_t, car celui-ci peut provoquer un signal défini par l'implémentation à soulever si la valeur est supérieure à 127, il est donc pas strictement portable).

+0

Est-ce que 'pack_s' n'est pas redondant? Si vous appelez 'pack_u' avec des expressions d'argument de type' int8_t', elles seront automatiquement converties. –

+0

Et '<=' dans le unpacks, je pense. –

+0

Oui, corrigé. – caf

3

Vous pouvez éviter des moulages avec des conversions implicites:

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

L'idée est que vous voyez, plutôt que « pour chaque paramètre, convertir correctement, shift « convertir tous les paramètres correctement Maj et les combiner. » et combinez-le ". Pas grand-chose, cependant.

Puis:

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

Sortie:

4 
5 
249 
-7 

C'est aussi portable que les uint32_t, uint8_t et int8_t types. Aucun d'entre eux n'est requis dans C99, et l'en-tête stdint.h n'est pas défini en C++ ou C89. Si les types existent et répondent aux exigences C99, cependant, le code fonctionnera. Bien sûr, en C, les fonctions de décompression nécessiteraient un paramètre de fonction au lieu d'un paramètre de modèle. Vous préférerez peut-être aussi en C++ si vous voulez écrire des boucles courtes pour le déballage. Pour traiter le fait que les types sont facultatifs, vous pouvez utiliser uint_least32_t, ce qui est requis dans C99. De manière similaire, uint_least8_t et int_least8_t. Vous devrez changer le code de pack_helper et 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))); 
} 

Pour être honnête, il est peu probable d'être la peine - les chances sont le reste de votre application est écrite sur l'hypothèse que int8_t etc existent. C'est une implémentation rare qui n'a pas un type de complément 8 bits et 32 ​​bits 2.

-1

Vous pouvez également laisser le compilateur faire le travail pour vous.

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

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

Etc.

+0

il ya un commentaire par M. Peters pourquoi cette approche est nonportable dans ce fil. – Anycorn

Questions connexes