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.


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


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)

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

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.


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

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

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

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

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

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

}

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.


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

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.


25
Nas arquiteturas x86 e x86_64, o esquema little endian é o nativo.
— MK aka Grisu
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.