konversikan big endian ke little endian di C [tanpa menggunakan func yang disediakan] [closed]

93

Saya perlu menulis fungsi untuk mengubah big endian menjadi little endian di C. Saya tidak dapat menggunakan fungsi library apa pun.

Alex Xander
sumber
5
nilai 16 bit? Nilai 32 bit? mengapung? sebuah array?
John Knoeller
20
waktu untuk memilih jawaban mungkin?
Aniket Inge
7
Memberi suara untuk dibuka kembali. Sama seperti stackoverflow.com/questions/105252/… untuk C ++. Kami hanya dapat mengedit untuk membuatnya lebih jelas.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

173

Dengan asumsi yang Anda butuhkan adalah pertukaran byte sederhana, cobalah sesuatu seperti

Konversi 16 bit unsigned:

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

Konversi 32-bit tanpa tanda tangan:

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

Ini menukar urutan byte dari posisi 1234 menjadi 4321. Jika masukan Anda adalah 0xdeadbeef, swap endian 32-bit mungkin memiliki keluaran 0xefbeadde.

Kode di atas harus dibersihkan dengan makro atau setidaknya konstanta, bukan angka ajaib, tapi semoga membantu apa adanya

EDIT: seperti jawaban lain yang ditunjukkan, ada platform, OS, dan set instruksi alternatif tertentu yang bisa JAUH lebih cepat dari yang di atas. Di kernel Linux ada makro (misalnya cpu_to_be32) yang menangani endianness dengan cukup baik. Tetapi alternatif ini khusus untuk lingkungan mereka. Dalam praktiknya, ketekunan paling baik ditangani dengan menggunakan campuran pendekatan yang tersedia

Sam Post
sumber
5
1 untuk menyebutkan metode khusus platform / perangkat keras. Program selalu dijalankan di beberapa perangkat keras, dan fitur perangkat keras selalu tercepat.
eonil
21
jika konversi 16 bit dilakukan seperti ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 menghasilkan satu rolinstruksi. Dan jika konversi 32 bit ditulis sebagai ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), kompilator yang sama menghasilkan satu bswapinstruksi.
pengguna666412
Saya tidak tahu seberapa efisien ini tetapi saya telah menukar urutan byte dengan bitfield seperti ini: di 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;}mana ini adalah bitfield dengan masing-masing 8 bidang 1 bit. Tapi saya tidak yakin apakah itu secepat saran lainnya. Untuk int gunakan union { int i; byte_t[sizeof(int)]; }untuk membalikkan byte demi byte dalam integer.
Ilian Zapryanov
Saya pikir ungkapannya harus: (num >> 8) | (num << 8) untuk membalik urutan byte dan BUKAN: ((num & 0xff) >> 8) | (num << 8), Contoh yang salah mendapatkan nol pada byte rendah.
jscom
@IlianZapryanov Mungkin +1 untuk kejelasan tetapi menggunakan bitfield dalam C seperti itu mungkin adalah cara yang paling tidak efisien untuk melakukannya.
sherrellbc
105

Dengan memasukkan:

#include <byteswap.h>

Anda bisa mendapatkan versi yang dioptimalkan dari fungsi pertukaran byte yang bergantung pada mesin. Kemudian, Anda dapat dengan mudah menggunakan fungsi berikut:

__bswap_32 (uint32_t input)

atau

__bswap_16 (uint16_t input)
Amir Mgh
sumber
3
Terima kasih atas jawaban Anda, tetapi saya tidak dapat menggunakan fungsi perpustakaan apa pun
Mark Ransom
4
Harus dibaca #include <byteswap.h>, lihat komentar di file .h itu sendiri. Posting ini berisi informasi yang berguna jadi saya memilih meskipun penulis mengabaikan persyaratan OP untuk tidak menggunakan fungsi lib.
Eli Rosencruft
30
Faktanya, fungsi __bswap_32 / __ bswap_16 sebenarnya adalah makro dan bukan fungsi perpustakaan, alasan lain untuk memilih.
Eli Rosencruft
7
Pemahaman saya adalah bahwa header ini tidak dijamin ada untuk semua sistem operasi di semua arsitektur. Saya belum menemukan cara portabel untuk menangani masalah endian.
Edward Falk
2
tidak ada di windows - setidaknya tidak saat melakukan kompilasi silang dari linux dengan mingw 32 atau 64 bit
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);
}

Pembaruan : Menambahkan pertukaran byte 64bit

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
sumber
Untuk varian int32_tdan int64_t, apa alasan di balik penyamaran ... & 0xFFFFdan ... & 0xFFFFFFFFULL? Apakah ada sesuatu yang terjadi dengan ekstensi tanda di sini yang tidak saya lihat? Juga, mengapa swap_int64kembali uint64_t? Bukankah begitu int64_t?
bgoodr
1
Swap_int64 yang mengembalikan uint64 memang merupakan kesalahan. Masking dengan nilai int bertanda memang untuk menghilangkan tandanya. Menggeser ke kanan menyuntikkan bit tanda di sebelah kiri. Kita bisa menghindari ini hanya dengan memanggil operasi swapping int unsigned.
chmike
Terima kasih. Anda mungkin ingin mengubah jenis nilai pengembalian swap_int64dalam jawaban Anda. 1 untuk jawaban yang membantu, BTW!
bgoodr
Apakah bitwise dan value endian bergantung?
MarcusJ
1
Yang LLtidak perlu di (u)swap_uint64()banyak seperti yang Ltidak dibutuhkan di (u)swap_uint32(). Yang Utidak dibutuhkan dalam uswap_uint64()banyak hal seperti Utidak diperlukan diuswap_uint32()
chux - Reinstate Monica
13

Ini adalah versi yang cukup umum; Saya belum menyusunnya, jadi mungkin ada kesalahan ketik, tetapi Anda harus mengerti,

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: Ini tidak dioptimalkan untuk kecepatan atau ruang. Ini dimaksudkan agar jelas (mudah di-debug) dan portabel.

Pembaruan 2018-04-04 Menambahkan assert () untuk menjebak kasus tidak valid n == 0, seperti yang terlihat oleh pemberi komentar @chux.

Michael J.
sumber
1
Anda dapat menggunakan xorSwap untuk kinerja yang lebih baik. Lebih suka versi generik ini di atas semua ukuran khusus ...
Saya mengujinya, ternyata ini lebih cepat dari xorSwap ... di x86. stackoverflow.com/questions/3128095/…
1
@nus - Salah satu keuntungan dari kode yang sangat sederhana adalah pengoptimal kompiler terkadang dapat membuatnya sangat cepat.
Michael J
@MichaelJ OTOH, versi 32 bit di atas dalam jawaban chmike dikompilasi menjadi satu bswapinstruksi oleh kompiler X86 yang layak dengan pengoptimalan diaktifkan. Versi dengan parameter untuk ukuran ini tidak dapat melakukan itu.
Alnitak
@Alnitak - Seperti yang saya katakan, saya tidak berusaha mengoptimalkan kode saya. Ketika pengguna nus menemukan bahwa kode berjalan sangat cepat (dalam satu kasus) saya baru saja menyebutkan gagasan umum bahwa kode sederhana seringkali dapat sangat dioptimalkan oleh kompiler. Kode saya berfungsi untuk berbagai macam kasus dan sangat mudah dipahami sehingga mudah untuk di-debug. Itu memenuhi tujuan saya.
Michael J
9

Jika Anda membutuhkan makro (mis. Sistem tertanam):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
kol
sumber
Makro ini baik-baik saja, tetapi ((x) >> 24) akan gagal jika bilangan bulat bertanda antara 0x80000000 dan 0xffffffff. Sebaiknya gunakan bitwise AND di sini. Catatan: ((x) << 24) sangat aman. (x) >> 8) juga akan gagal jika 16 bit tinggi bukan nol (atau nilai 16 bit bertanda disediakan).
2
@ PacMan-- Makro ini dimaksudkan untuk digunakan hanya untuk menukar bilangan bulat tak bertanda . Makanya ada UINTdi nama mereka.
kol
Ya, benar, maaf atas kebisingannya. Bukankah lebih baik menyematkan typecast?
5

Edit: Ini adalah fungsi perpustakaan. Mengikuti mereka adalah cara manual untuk melakukannya.

Saya sangat terkejut dengan jumlah orang yang tidak mengetahui __byteswap_ushort, __byteswap_ulong, dan __byteswap_uint64 . Tentu mereka spesifik untuk Visual C ++, tetapi mereka mengkompilasi menjadi kode yang bagus pada arsitektur x86 / IA-64. :)

Berikut adalah penggunaan bswapinstruksi secara eksplisit , ditarik dari halaman ini . Perhatikan bahwa bentuk intrinsik di atas akan selalu lebih cepat dari ini , saya hanya menambahkannya untuk memberikan jawaban tanpa rutinitas perpustakaan.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}
Sam Harwell
sumber
21
Untuk pertanyaan C, Anda menyarankan sesuatu yang spesifik untuk Visual C ++?
Alok Singhal
3
@Alok: Visual C ++ adalah produk Microsoft. Ini berfungsi dengan baik untuk mengkompilasi kode C. :)
Sam Harwell
20
Mengapa mengejutkan Anda bahwa banyak orang tidak mengetahui implementasi byteswapping khusus Microsoft?
dreamlax
36
Keren, itu info bagus untuk siapa saja yang mengembangkan produk sumber tertutup yang tidak perlu portabel atau memenuhi standar.
Sam Post
6
@Alok, OP tidak menyebutkan compiler | OS. Seseorang diperbolehkan memberikan jawaban sesuai dengan pengalamannya dengan seperangkat alat tertentu.
Aniket Inge
5

Sebagai lelucon:


#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
sumber
7
HAHAHAHAHA. Ha ha ha. Ha. Ha? (Lelucon apa?)
3
apakah Anda menarik ini dari beberapa repositori sumber Windows? :)
hochl
Nodejs menggunakan teknik ini! github.com/nodejs/node/blob/…
Justin Moser
Penasaran untuk digunakan int i, size_t sizeofIntdan bukan tipe yang sama untuk keduanya.
chux - Kembalikan Monica
5

berikut adalah cara menggunakan instruksi SSSE3 pshufb menggunakan intrinsik Intel, dengan asumsi Anda memiliki kelipatan 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
sumber
3

Akankah ini berhasil / lebih cepat?

 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
sumber
2
Saya pikir maksud Anda char, bukan byte.
dreamlax
Dengan menggunakan strategi ini, solusi dengan suara terbanyak dibandingkan dengan Anda adalah solusi yang setara dan paling efisien dan portabel. Namun solusi yang saya usulkan (suara terbanyak kedua) membutuhkan lebih sedikit operasi dan harus lebih efisien.
chmike
1

Berikut adalah fungsi yang telah saya gunakan - diuji dan berfungsi pada semua tipe data dasar:

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

}
penjual tiket
sumber
2
Kode bergantung pada asumsi yang sangat masuk akal: sourcediselaraskan sesuai kebutuhan - namun jika asumsi tersebut tidak berlaku, kodenya adalah UB.
chux - Kembalikan Monica
1

EDIT: Fungsi ini hanya menukar endianness dari kata-kata 16 bit yang selaras. Fungsi yang sering diperlukan untuk encoding UTF-16 / UCS-2. EDIT AKHIR.

Jika Anda ingin mengubah endianess dari blok memori, Anda dapat menggunakan pendekatan saya yang sangat cepat. Larik memori Anda harus memiliki ukuran kelipatan 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);
}

Fungsi semacam ini berguna untuk mengubah endianess file Unicode UCS-2 / UTF-16.

Patrick Schlüter
sumber
CHAR_BIT #define hilang untuk membuat kode lengkap.
Tõnu Samuel
Oke, saya menambahkan termasuk yang hilang.
Patrick Schlüter
di sini adalah tautan ke pertukaran di C ++, saya tidak t know if itsecepat saran tetapi itu wokrs
Ilian Zapryanov
CHAR_BITbukannya 8penasaran karena 0xFF00FF00FF00FF00ULLbergantung pada CHAR_BIT == 8. Perhatikan bahwa LLtidak diperlukan dalam konstanta.
chux - Kembalikan Monica
Anda benar. Hanya aja dengan CHAR_BITuntuk menambah eksposur makro itu. Sedangkan untuk LL, ini lebih merupakan anotasi daripada apa pun. Ini juga kebiasaan yang saya tangkap sejak lama dengan kompiler buggy (pra standar) yang tidak akan melakukan hal yang benar.
Patrick Schlüter
1

Cuplikan kode ini dapat mengubah 32bit nomor Endian kecil menjadi nomor Endian Besar.

#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
sumber
Terima kasih @YuHao Saya baru di sini, tidak tahu cara memformat Teks.
Kaushal Billore
2
Penggunaan ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);mungkin lebih cepat pada beberapa platform (mis. Mendaur ulang konstanta mask AND). Sebagian besar kompiler akan melakukannya, tetapi beberapa kompiler sederhana tidak dapat mengoptimalkannya untuk Anda.
-7

Jika Anda menjalankan prosesor x86 atau x86_64, big endian adalah native. begitu

untuk nilai 16 bit

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

untuk nilai 32 bit

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

Ini bukan solusi yang paling efisien kecuali jika kompilator mengenali bahwa ini adalah manipulasi tingkat byte dan menghasilkan kode pertukaran byte. Tetapi itu tidak bergantung pada trik tata letak memori dan dapat diubah menjadi makro dengan cukup mudah.

John Knoeller
sumber
25
Pada arsitektur x86 dan x86_64, skema little endian adalah yang asli.
MK aka Grisu