Preciso escrever uma função para converter big endian em little endian em C. Não consigo usar nenhuma função de biblioteca.
Preciso escrever uma função para converter big endian em little endian em C. Não consigo usar nenhuma função de biblioteca.
Respostas:
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
((num & 0xff) >> 8) | (num << 8)
, o gcc 4.8.3 gera uma única rol
instruçã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 bswap
instrução.
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.
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)
#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.
#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);
}
int32_t
e int64_t
, qual é o raciocínio por trás do mascaramento de ... & 0xFFFF
e ... & 0xFFFFFFFFULL
? Há algo acontecendo com a extensão de sinal aqui que não estou vendo? Além disso, por que está swap_int64
voltando uint64_t
? Não deveria ser assim int64_t
?
swap_int64
em sua resposta. 1 para a resposta útil, BTW!
LL
são desnecessários (u)swap_uint64()
bem como um L
não é necessário (u)swap_uint32()
. O U
não é necessário uswap_uint64()
tanto quanto o U
não é necessário emuswap_uint32()
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.
bswap
instruçã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.
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))
UINT
em seu nome.
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 bswap
instruçã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;
}
}
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;
}
int i, size_t sizeofInt
e não do mesmo tipo para ambos.
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 int
s:
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;
}
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];
char
, não byte
.
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;
}
}
source
está alinhado conforme necessário - mas se essa suposição não for válida, o código é UB.
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.
t know if it
tão rápido quanto as sugestões, mas funciona: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
em vez de 8
é curioso, pois 0xFF00FF00FF00FF00ULL
depende de CHAR_BIT == 8
. Observe que LL
não é necessário na constante.
CHAR_BIT
para 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.
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);
}
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.