convertir big endian en little endian en C [sans utiliser la fonction fournie] [fermé]

91

J'ai besoin d'écrire une fonction pour convertir big endian en little endian en C. Je ne peux utiliser aucune fonction de bibliothèque.

Alex Xander
la source
5
une valeur de 16 bits? Valeur 32 bits? flotte? un tableau?
John Knoeller
19
le temps de choisir une réponse peut-être?
Aniket Inge
7
Voter pour rouvrir. Identique à stackoverflow.com/questions/105252/… pour C ++. Nous pourrions simplement modifier pour rendre cela plus clair.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Réponses:

168

En supposant que vous ayez besoin d'un simple échange d'octets, essayez quelque chose comme

Conversion 16 bits non signée:

swapped = (num>>8) | (num<<8);

Conversion 32 bits non signée:

swapped = ((num>>24)&0xff) | // move byte 3 to byte 0
                    ((num<<8)&0xff0000) | // move byte 1 to byte 2
                    ((num>>8)&0xff00) | // move byte 2 to byte 1
                    ((num<<24)&0xff000000); // byte 0 to byte 3

Cela permute les ordres d'octets des positions 1234 à 4321. Si votre entrée l'était 0xdeadbeef, un swap endian 32 bits pourrait avoir une sortie de 0xefbeadde.

Le code ci-dessus devrait être nettoyé avec des macros ou au moins des constantes au lieu de nombres magiques, mais j'espère que cela aide tel quel

EDIT: comme une autre réponse l'a souligné, il existe des alternatives spécifiques à la plate-forme, au système d'exploitation et au jeu d'instructions qui peuvent être BEAUCOUP plus rapides que les précédentes. Dans le noyau Linux, il y a des macros (cpu_to_be32 par exemple) qui gèrent assez bien l'endianness. Mais ces alternatives sont spécifiques à leurs environnements. Dans la pratique, l'endianité est mieux traitée en utilisant un mélange d'approches disponibles

Sam Post
la source
5
+1 pour mentionner les méthodes spécifiques à la plate-forme / au matériel. Les programmes sont toujours exécutés sur certains matériels et les fonctionnalités matérielles sont toujours les plus rapides.
eonil
21
si la conversion 16 bits est effectuée comme ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 génère une seule rolinstruction. Et si la conversion 32 bits est écrite comme ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), le même compilateur génère une seule bswapinstruction.
user666412
Je ne sais pas à quel point c'est efficace, mais j'ai permuté l'ordre des octets avec des champs de bits comme celui-ci: struct byte_t reverse(struct byte_t b) { struct byte_t rev; rev.ba = b.bh; rev.bb = b.bg; rev.bc = b.bf; rev.bd = b.be; rev.be = b.bd; rev.bf = b.bc; rev.bg = b.bb; rev.bh = b.ba; return rev;}où c'est un champ de bits avec 8 champs de 1 bit chacun. Mais je ne sais pas si c'est aussi rapide que les autres suggestions. Pour les entiers, utilisez union { int i; byte_t[sizeof(int)]; }pour inverser octet par octet dans l'entier.
Ilian Zapryanov le
Je pense que l'expression doit être: (num >> 8) | (num << 8) pour inverser l'ordre des octets et NON: ((num & 0xff) >> 8) | (num << 8), Le mauvais exemple obtient zéro dans l'octet de poids faible.
jscom
@IlianZapryanov Peut-être +1 pour plus de clarté, mais utiliser des champs de bits en C comme ça est probablement le moyen le moins efficace de le faire.
sherrellbc
104

En incluant:

#include <byteswap.h>

vous pouvez obtenir une version optimisée des fonctions de permutation d'octets dépendant de la machine. Ensuite, vous pouvez facilement utiliser les fonctions suivantes:

__bswap_32 (uint32_t input)

ou

__bswap_16 (uint16_t input)
Amir Mgh
la source
3
Merci pour votre réponse, mais je ne peux utiliser aucune fonction de bibliothèque
Mark Ransom
4
Devrait lire #include <byteswap.h>, voir le commentaire dans le fichier .h lui-même. Ce message contient des informations utiles, j'ai donc voté à la hausse malgré que l'auteur ignore l'exigence de l'OP de ne pas utiliser une fonction lib.
Eli Rosencruft
30
En fait, les fonctions __bswap_32 / __ bswap_16 sont en fait des macros et non des fonctions de bibliothèque, une autre raison de voter à la hausse.
Eli Rosencruft
7
Je crois comprendre que cet en-tête n'est pas garanti pour tous les systèmes d'exploitation sur toutes les architectures. Je n'ai pas encore trouvé de moyen portable de traiter les problèmes endians.
Edward Falk
2
n'existe pas sur Windows - du moins pas lors de la compilation croisée à partir de Linux avec mingw 32 ou 64 bits
bph
61
#include <stdint.h>


//! Byte swap unsigned short
uint16_t swap_uint16( uint16_t val ) 
{
    return (val << 8) | (val >> 8 );
}

//! Byte swap short
int16_t swap_int16( int16_t val ) 
{
    return (val << 8) | ((val >> 8) & 0xFF);
}

//! Byte swap unsigned int
uint32_t swap_uint32( uint32_t val )
{
    val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); 
    return (val << 16) | (val >> 16);
}

//! Byte swap int
int32_t swap_int32( int32_t val )
{
    val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF ); 
    return (val << 16) | ((val >> 16) & 0xFFFF);
}

Mise à jour : Ajout de l'échange d'octets 64 bits

int64_t swap_int64( int64_t val )
{
    val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
    val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
    return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}

uint64_t swap_uint64( uint64_t val )
{
    val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
    val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
    return (val << 32) | (val >> 32);
}
chmike
la source
Pour les variantes int32_tet int64_t, quel est le raisonnement derrière le masquage de ... & 0xFFFFet ... & 0xFFFFFFFFULL? Y a-t-il quelque chose qui se passe avec l'extension de signe ici que je ne vois pas? Aussi, pourquoi swap_int64revient-il uint64_t? Cela ne devrait-il pas être le cas int64_t?
bgoodr
1
Le swap_int64 renvoyant un uint64 est en effet une erreur. Le masquage avec des valeurs int signées consiste en effet à supprimer le signe. Le décalage vers la droite injecte le bit de signe sur la gauche. Nous pourrions éviter cela en appelant simplement l'opération de permutation int unsigned.
chmike
Merci. Vous souhaiterez peut-être modifier le type de valeur de retour pour swap_int64dans votre réponse. +1 pour la réponse utile, BTW!
bgoodr
L'endian au niveau du bit et de la valeur dépend-il?
MarcusJ
1
Le LLsont inutiles dans un (u)swap_uint64()peu comme un Ln'est pas nécessaire dans (u)swap_uint32(). Le Un'est pas nécessaire dans un uswap_uint64()peu comme le Un'est pas nécessaire dansuswap_uint32()
chux - Rétablir Monica
13

Voici une version assez générique; Je ne l'ai pas compilé, donc il y a probablement des fautes de frappe, mais vous devriez avoir l'idée,

void SwapBytes(void *pv, size_t n)
{
    assert(n > 0);

    char *p = pv;
    size_t lo, hi;
    for(lo=0, hi=n-1; hi>lo; lo++, hi--)
    {
        char tmp=p[lo];
        p[lo] = p[hi];
        p[hi] = tmp;
    }
}
#define SWAP(x) SwapBytes(&x, sizeof(x));

NB: Ceci n'est pas optimisé pour la vitesse ou l'espace. Il se veut clair (facile à déboguer) et portable.

Mise à jour 04/04/2018 Ajout de assert () pour piéger le cas invalide de n == 0, comme repéré par le commentateur @chux.

Michael J
la source
1
vous pouvez utiliser xorSwap pour de meilleures performances. Préférez cette version générique à toutes les versions spécifiques à la taille ...
Je l'ai testé, il s'avère que c'est plus rapide que xorSwap ... sur x86. stackoverflow.com/questions/3128095/…
1
@nus - L'un des avantages d'un code très simple est que l'optimiseur du compilateur peut parfois le rendre très rapide.
Michael J
@MichaelJ OTOH, la version 32 bits ci-dessus dans la réponse de chmike est compilée en une seule bswapinstruction par un compilateur X86 décent avec l'optimisation activée. Cette version avec un paramètre pour la taille ne pouvait pas faire cela.
Alnitak
@Alnitak - Comme je l'ai dit, je n'ai fait aucun effort pour optimiser mon code. Lorsque l'utilisateur nus a constaté que le code fonctionnait très rapidement (dans un cas), je viens de mentionner l'idée générale que du code simple peut souvent être hautement optimisé par un compilateur. Mon code fonctionne pour une grande variété de cas et il est assez facile à comprendre et donc facile à déboguer. Cela a atteint mes objectifs.
Michael J
9

Si vous avez besoin de macros (par exemple, système embarqué):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
kol
la source
Ces macros sont bien, mais ((x) >> 24) échouera lorsqu'un entier signé est compris entre 0x80000000 et 0xffffffff. C'est une bonne idée d'utiliser le bit AND ici. Remarque: ((x) << 24) est parfaitement sûr. (x) >> 8) échouera également si 16 bits élevés sont différents de zéro (ou si une valeur 16 bits signée est fournie).
2
@ PacMan - Ces macros sont destinées à être utilisées pour échanger uniquement des entiers non signés . C'est pourquoi il y a le UINTdans leur nom.
kol
Oui, c'est vrai, désolé pour le bruit. Ne serait-il pas préférable d'intégrer un transtypage?
5

Edit: ce sont des fonctions de bibliothèque. Les suivre est la manière manuelle de le faire.

Je suis absolument abasourdi par le nombre de personnes qui ne connaissent pas __byteswap_ushort, __byteswap_ulong et __byteswap_uint64 . Bien sûr, ils sont spécifiques à Visual C ++, mais ils se compilent en un code délicieux sur les architectures x86 / IA-64. :)

Voici une utilisation explicite de l' bswapinstruction, tirée de cette page . Notez que la forme intrinsèque ci-dessus sera toujours plus rapide que cela , je l'ai seulement ajoutée pour donner une réponse sans routine de bibliothèque.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}
Sam Harwell
la source
21
Pour une question C, vous suggérez quelque chose qui est spécifique à Visual C ++?
Alok Singhal
3
@Alok: Visual C ++ est un produit de Microsoft. Cela fonctionne très bien pour compiler du code C. :)
Sam Harwell
20
Pourquoi est-ce que cela vous étonne que de nombreuses personnes ne connaissent pas les implémentations spécifiques à Microsoft de l'échange d'octets?
dreamlax
36
Cool, c'est une bonne information pour quiconque développe un produit de source fermée qui n'a pas besoin d'être portable ou conforme aux normes.
Sam Post le
6
@Alok, OP n'a pas mentionné le compilateur | OS. Une personne est autorisée à donner des réponses en fonction de son expérience avec un ensemble d'outils particulier.
Aniket Inge
5

Comme une blague:


#include <stdio.h>

int main (int argc, char *argv[])
{
    size_t sizeofInt = sizeof (int);
    int i;

    union
    {
        int x;
        char c[sizeof (int)];
    } original, swapped;

    original.x = 0x12345678;

    for (i = 0; i < sizeofInt; i++)
        swapped.c[sizeofInt - i - 1] = original.c[i];

    fprintf (stderr, "%x\n", swapped.x);

    return 0;
}
dreamlax
la source
7
HAHAHAHAHA. Hahaha. Ha. Ha? (Quelle blague?)
3
avez-vous extrait cela d'un référentiel source Windows? :)
hochl
Nodejs utilise cette technique! github.com/nodejs/node/blob/…
Justin Moser
Curieux à utiliser int i, size_t sizeofIntet pas du même type pour les deux.
chux - Réintégrer Monica le
5

voici un moyen d'utiliser l'instruction SSSE3 pshufb en utilisant son Intel intrinsèque, en supposant que vous ayez un multiple de 4 ints:

unsigned int *bswap(unsigned int *destination, unsigned int *source, int length) {
    int i;
    __m128i mask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
    for (i = 0; i < length; i += 4) {
        _mm_storeu_si128((__m128i *)&destination[i],
        _mm_shuffle_epi8(_mm_loadu_si128((__m128i *)&source[i]), mask));
    }
    return destination;
}
jcomeau_ictx
la source
3

Est-ce que cela fonctionnera / sera plus rapide?

 uint32_t swapped, result;

((byte*)&swapped)[0] = ((byte*)&result)[3];
((byte*)&swapped)[1] = ((byte*)&result)[2];
((byte*)&swapped)[2] = ((byte*)&result)[1];
((byte*)&swapped)[3] = ((byte*)&result)[0];
Paul
la source
2
Je pense que tu veux dire char, non byte.
dreamlax
En utilisant cette stratégie, la solution avec le plus de votes par rapport à la vôtre est équivalente et la plus efficace et portable. Cependant, la solution que je propose (deuxième plus de votes) nécessite moins d'opérations et devrait être plus efficace.
chmike
1

Voici une fonction que j'ai utilisée - testée et fonctionne sur n'importe quel type de données de base:

//  SwapBytes.h
//
//  Function to perform in-place endian conversion of basic types
//
//  Usage:
//
//    double d;
//    SwapBytes(&d, sizeof(d));
//

inline void SwapBytes(void *source, int size)
{
    typedef unsigned char TwoBytes[2];
    typedef unsigned char FourBytes[4];
    typedef unsigned char EightBytes[8];

    unsigned char temp;

    if(size == 2)
    {
        TwoBytes *src = (TwoBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[1];
        (*src)[1] = temp;

        return;
    }

    if(size == 4)
    {
        FourBytes *src = (FourBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[3];
        (*src)[3] = temp;

        temp = (*src)[1];
        (*src)[1] = (*src)[2];
        (*src)[2] = temp;

        return;
    }

    if(size == 8)
    {
        EightBytes *src = (EightBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[7];
        (*src)[7] = temp;

        temp = (*src)[1];
        (*src)[1] = (*src)[6];
        (*src)[6] = temp;

        temp = (*src)[2];
        (*src)[2] = (*src)[5];
        (*src)[5] = temp;

        temp = (*src)[3];
        (*src)[3] = (*src)[4];
        (*src)[4] = temp;

        return;
    }

}
billetterie
la source
2
Le code repose sur une hypothèse très raisonnable: sourceest aligné selon les besoins - mais si cette hypothèse ne tient pas, le code est UB.
chux - Réintégrer Monica le
1

EDIT: Cette fonction n'échange que l'endianité des mots alignés de 16 bits. Une fonction souvent nécessaire pour les encodages UTF-16 / UCS-2. MODIFIER FIN.

Si vous souhaitez modifier la finalité d'un bloc de mémoire, vous pouvez utiliser mon approche extrêmement rapide. Votre matrice de mémoire doit avoir une taille multiple de 8.

#include <stddef.h>
#include <limits.h>
#include <stdint.h>

void ChangeMemEndianness(uint64_t *mem, size_t size) 
{
uint64_t m1 = 0xFF00FF00FF00FF00ULL, m2 = m1 >> CHAR_BIT;

size = (size + (sizeof (uint64_t) - 1)) / sizeof (uint64_t);
for(; size; size--, mem++)
  *mem = ((*mem & m1) >> CHAR_BIT) | ((*mem & m2) << CHAR_BIT);
}

Ce type de fonction est utile pour modifier l'extrémité des fichiers Unicode UCS-2 / UTF-16.

Patrick Schlüter
la source
CHAR_BIT #define est manquant pour que le code soit complet.
Tõnu Samuel
Ok, j'ai ajouté les inclusions manquantes.
Patrick Schlüter
voici un lien vers un swap en C ++, je ne suis pas t know if itaussi rapide que les suggestions mais ça marche: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov
CHAR_BITau lieu de 8est curieux comme 0xFF00FF00FF00FF00ULLdépend de CHAR_BIT == 8. Notez que ce LLn'est pas nécessaire dans la constante.
chux - Réintégrer Monica le
Vous avez raison chux. Seulement écrit avec CHAR_BITpour augmenter l'exposition de cette macro. Quant au LL, c'est plus une annotation qu'autre chose. C'est aussi une habitude que j'ai prise il y a longtemps avec des compilateurs buggy (pré-standard) qui ne feraient pas la bonne chose.
Patrick Schlüter
1

Cet extrait de code peut convertir un petit nombre Endian 32 bits en un nombre Big Endian.

#include <stdio.h>
main(){    
    unsigned int i = 0xfafbfcfd;
    unsigned int j;    
    j= ((i&0xff000000)>>24)| ((i&0xff0000)>>8) | ((i&0xff00)<<8) | ((i&0xff)<<24);    
    printf("unsigned int j = %x\n ", j);    
}
Kaushal Billore
la source
Merci @YuHao Je suis nouveau ici, je ne sais pas comment formater le texte.
Kaushal Billore
2
L'utilisation ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);peut être plus rapide sur certaines plates-formes (par exemple, le recyclage des constantes de masque AND). La plupart des compilateurs le feraient, cependant, mais certains compilateurs simples ne sont pas en mesure de l'optimiser pour vous.
-7

Si vous utilisez un processeur x86 ou x86_64, le big endian est natif. alors

pour les valeurs 16 bits

unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);

pour les valeurs 32 bits

unsigned int   iBigE = value;
unsigned int   iLittleE = ((iBigE & 0xFF) << 24)
                        | ((iBigE & 0xFF00) << 8)
                        | ((iBigE >> 8) & 0xFF00)
                        | (iBigE >> 24);

Ce n'est pas la solution la plus efficace, sauf si le compilateur reconnaît qu'il s'agit d'une manipulation au niveau des octets et génère du code de permutation d'octets. Mais cela ne dépend d'aucune astuce de disposition de la mémoire et peut être transformé en macro assez facilement.

John Knoeller
la source
25
Sur les architectures x86 et x86_64, le schéma little endian est le schéma natif.
MK aka Grisu