converter big endian em little endian em C [sem usar a função fornecida] [fechado]

91

Preciso escrever uma função para converter big endian em little endian em C. Não consigo usar nenhuma função de biblioteca.

Alex Xander
fonte
5
um valor de 16 bits? Valor de 32 bits? flutuador? uma matriz?
John Knoeller
19
hora de escolher uma resposta, talvez?
Aniket Inge
7
Votando para reabrir. O mesmo que stackoverflow.com/questions/105252/… para C ++. Poderíamos apenas editar para tornar isso mais claro.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respostas:

168

Supondo que você precisa de uma simples troca de bytes, tente algo como

Conversão de 16 bits sem sinal:

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

Conversão de 32 bits sem sinal:

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

Isso troca as ordens de bytes das posições 1234 para 4321. Se a sua entrada foi 0xdeadbeef, um swap endian de 32 bits pode ter a saída de 0xefbeadde.

O código acima deve ser limpo com macros ou pelo menos constantes em vez de números mágicos, mas espero que ajude como está

EDITAR: como outra resposta apontou, existem alternativas específicas de plataforma, sistema operacional e conjunto de instruções que podem ser MUITO mais rápidas do que as anteriores. No kernel do Linux existem macros (cpu_to_be32 por exemplo) que lidam muito bem com o endianness. Mas essas alternativas são específicas para seus ambientes. Na prática, o endianismo é melhor tratado usando uma combinação de abordagens disponíveis

Sam Post
fonte
5
+1 para mencionar métodos específicos de plataforma / hardware. Os programas são sempre executados em algum hardware e os recursos de hardware são sempre mais rápidos.
eonil
21
se a conversão de 16 bits for feita como ((num & 0xff) >> 8) | (num << 8), o gcc 4.8.3 gera uma única rolinstrução. E se a conversão de 32 bits for escrita como ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), o mesmo compilador gerará uma única bswapinstrução.
user666412
Eu não sei o quão eficiente isso é, mas eu troquei a ordem dos bytes 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;}por campos de bits como este: onde este é um campo de bits com 8 campos de 1 bit cada. Mas não tenho certeza se isso é tão rápido quanto as outras sugestões. Para ints, use union { int i; byte_t[sizeof(int)]; }para inverter byte a byte no inteiro.
Ilian Zapryanov
Acho que a expressão deve ser: (num >> 8) | (num << 8) para inverter a ordem dos bytes e NÃO: ((num & 0xff) >> 8) | (num << 8), O exemplo errado obtém zero no byte inferior.
jscom
@IlianZapryanov Talvez +1 para maior clareza, mas usar bitfields em C dessa forma é provavelmente a maneira menos eficiente de fazer isso.
Sherrellbc
104

Incluindo:

#include <byteswap.h>

você pode obter uma versão otimizada das funções de troca de bytes dependentes da máquina. Então, você pode usar facilmente as seguintes funções:

__bswap_32 (uint32_t input)

ou

__bswap_16 (uint16_t input)
Amir Mgh
fonte
3
Obrigado pela sua resposta, mas não consigo usar nenhuma função de biblioteca
Mark Ransom
4
Deve ler #include <byteswap.h>, veja o comentário no próprio arquivo .h. Esta postagem contém informações úteis, então votei a favor, apesar de o autor ignorar o requisito do OP de não usar uma função lib.
Eli Rosencruft
30
Na verdade, as funções __bswap_32 / __ bswap_16 são macros e não funções de biblioteca, outro motivo para votar positivamente.
Eli Rosencruft
7
Meu entendimento é que não há garantia de que esse cabeçalho exista para todos os sistemas operacionais em todas as arquiteturas. Ainda não encontrei uma maneira portátil de lidar com os problemas endian.
Edward Falk
2
não existe no Windows - pelo menos não durante a compilação cruzada do Linux com o mingw de 32 ou 64 bits
bph
62
#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);
}

Atualização : Adicionada troca de bytes de 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
fonte
Para as variantes int32_te int64_t, qual é o raciocínio por trás do mascaramento de ... & 0xFFFFe ... & 0xFFFFFFFFULL? Há algo acontecendo com a extensão de sinal aqui que não estou vendo? Além disso, por que está swap_int64voltando uint64_t? Não deveria ser assim int64_t?
bgoodr
1
O swap_int64 retornando um uint64 é realmente um erro. O mascaramento com valores int assinados é, de fato, para remover o sinal. O deslocamento para a direita injeta o bit do sinal à esquerda. Poderíamos evitar isso simplesmente chamando a operação de troca int sem sinal.
chmike
Obrigado. Você pode querer alterar o tipo de valor de retorno de swap_int64em sua resposta. 1 para a resposta útil, BTW!
bgoodr
O bit a bit e o valor endian são dependentes?
MarcusJ
1
O LLsão desnecessários (u)swap_uint64()bem como um Lnão é necessário (u)swap_uint32(). O Unão é necessário uswap_uint64()tanto quanto o Unão é necessário emuswap_uint32()
chux - Reintegrar Monica
13

Aqui está uma versão bastante genérica; Eu não compilei, então provavelmente há erros de digitação, mas você deve ter uma ideia,

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: Isso não éotimizado para velocidade ou espaço. Ele se destina a ser claro (fácil de depurar) e portátil.

Atualização 04-04-2018 Adicionado o assert () para capturar o caso inválido de n == 0, conforme observado pelo comentador @chux.

Michael J
fonte
1
você pode usar xorSwap para melhor desempenho. Prefira esta versão genérica acima de todas as específicas de tamanho ...
Eu testei, ele é mais rápido que o xorSwap ... no x86. stackoverflow.com/questions/3128095/…
1
@nus - Uma das vantagens de um código muito simples é que o otimizador do compilador às vezes pode torná-lo muito rápido.
Michael J
@MichaelJ OTOH, a versão de 32 bits acima na resposta de chmike é compilada para uma única bswapinstrução por um compilador X86 decente com otimização habilitada. Esta versão com um parâmetro para o tamanho não poderia fazer isso.
Alnitak de
@Alnitak - Como eu disse, não fiz nenhum esforço para otimizar meu código. Quando o usuário nus descobriu que o código era executado muito rápido (em um caso), acabei de mencionar a ideia geral de que um código simples pode frequentemente ser altamente otimizado por um compilador. Meu código funciona para uma ampla variedade de casos e é muito fácil de entender e, portanto, fácil de depurar. Isso atendeu aos meus objetivos.
Michael J,
8

Se você precisar de macros (por exemplo, sistema incorporado):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
kol
fonte
Essas macros são boas, mas ((x) >> 24) irá falhar quando um número inteiro assinado estiver entre 0x80000000 e 0xffffffff. É uma boa ideia usar E bit a bit aqui. Nota: ((x) << 24) é perfeitamente seguro. (x) >> 8) também falhará se 16 bits altos forem diferentes de zero (ou um valor de 16 bits com sinal for fornecido).
2
@ PacMan-- Essas macros devem ser usadas para trocar apenas inteiros sem sinal . É por isso que existe o UINTem seu nome.
kol
Sim, é verdade, desculpe pelo barulho. Não seria melhor incorporar um typecast?
5

Editar: são funções de biblioteca. Segui-los é a maneira manual de fazer isso.

Estou absolutamente surpreso com o número de pessoas que desconhecem __byteswap_ushort, __byteswap_ulong e __byteswap_uint64 . Claro que eles são específicos do Visual C ++, mas são compilados em alguns códigos deliciosos nas arquiteturas x86 / IA-64. :)

Aqui está um uso explícito da bswapinstrução, extraída desta página . Observe que a forma intrínseca acima sempre será mais rápida do que isso , eu apenas a adicionei para dar uma resposta sem uma rotina de biblioteca.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}
Sam Harwell
fonte
21
Para uma pergunta sobre C, você está sugerindo algo específico do Visual C ++?
Alok Singhal
3
@Alok: Visual C ++ é um produto da Microsoft. Ele funciona muito bem para compilar código C. :)
Sam Harwell
20
Por que você fica surpreso com o fato de muitas pessoas não estarem cientes das implementações específicas da Microsoft de troca de bytes?
dreamlax
36
Legal, essa é uma boa informação para quem desenvolve um produto de código fechado que não precisa ser portátil ou compatível com os padrões.
Sam Post de
6
@Alok, OP não mencionou o sistema operacional | do compilador. Uma pessoa pode dar respostas de acordo com sua experiência com um determinado conjunto de ferramentas.
Aniket Inge
5

Como uma piada:


#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
fonte
7
HAHAHAHAHA. Hahaha. Ha. Ha? (Que piada?)
3
você puxou isso de algum repositório de origem do Windows? :)
hochl
O Nodejs usa essa técnica! github.com/nodejs/node/blob/…
Justin Moser
Curioso para usar int i, size_t sizeofInte não do mesmo tipo para ambos.
chux - Reintegrar Monica,
5

aqui está uma maneira de usar a instrução SSSE3 pshufb usando seu intrínseco Intel, supondo que você tenha um múltiplo 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
fonte
3

Isso funcionará / será mais rápido?

 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];
Paulo
fonte
2
Eu acho que você quer dizer char, não byte.
dreamlax
Usando essa estratégia, a solução com mais votos em comparação com a sua é equivalente e a mais eficiente e portátil. No entanto, a solução que proponho (o segundo maior número de votos) precisa de menos operações e deve ser mais eficiente.
chmike
1

Esta é uma função que tenho usado - testei e funciona em qualquer tipo de dados básico:

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

}
bilheteiro
fonte
2
O código se baseia em uma suposição bastante razoável: sourceestá alinhado conforme necessário - mas se essa suposição não for válida, o código é UB.
chux - Reintegrar Monica,
1

EDIT: Esta função apenas troca o endianness de palavras alinhadas de 16 bits. Uma função freqüentemente necessária para codificações UTF-16 / UCS-2. EDIT END.

Se você quiser mudar a duração de um bloco de memória, pode usar minha abordagem incrivelmente rápida. Seu array de memória deve ter um tamanho múltiplo 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);
}

Este tipo de função é útil para alterar o endianess de arquivos Unicode UCS-2 / UTF-16.

Patrick Schlüter
fonte
CHAR_BIT #define está faltando para completar o código.
Tõnu Samuel
Ok, adicionei os inclui que faltam.
Patrick Schlüter
aqui está um link para uma troca em C ++, eu não sou t know if ittão rápido quanto as sugestões, mas funciona: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov
CHAR_BITem vez de 8é curioso, pois 0xFF00FF00FF00FF00ULLdepende de CHAR_BIT == 8. Observe que LLnão é necessário na constante.
chux - Reintegrar Monica em
Você está certo chux. Escrevi apenas com CHAR_BITpara aumentar a exposição dessa macro. Quanto ao LL, é mais uma anotação do que qualquer outra coisa. Também é um hábito que peguei há muito tempo com compiladores buggy (pré-padrão) que não fariam a coisa certa.
Patrick Schlüter
1

Este trecho de código pode converter um pequeno número Endian de 32 bits em um número 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
fonte
Obrigado @YuHao Sou novo por aqui, não sei formatar o Texto.
Kaushal Billore
2
O uso ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);pode ser mais rápido em algumas plataformas (por exemplo, reciclar as constantes da máscara AND). A maioria dos compiladores faria isso, mas alguns compiladores simples não são capazes de otimizá-lo para você.
-7

Se você estiver executando em um processador x86 ou x86_64, o big endian é nativo. tão

para valores de 16 bits

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

para valores de 32 bits

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

Essa não é a solução mais eficiente, a menos que o compilador reconheça que se trata de uma manipulação em nível de byte e gere o código de troca de bytes. Mas não depende de nenhum truque de layout de memória e pode ser transformado em uma macro com bastante facilidade.

John Knoeller
fonte
25
Nas arquiteturas x86 e x86_64, o esquema little endian é o nativo.
MK aka Grisu