C'de big endian'ı little endian'a dönüştür [sağlanan işlevi kullanmadan] [kapalı]


93

C'de big endian'ı little endian'a dönüştürmek için bir fonksiyon yazmam gerekiyor. Herhangi bir kütüphane fonksiyonu kullanamıyorum.


5
16 bitlik bir değer? 32 bit değer? float? bir dizi mi?
John Knoeller

20
belki bir cevap seçme zamanı?
Aniket Inge

7
Yeniden açılması için oylama. C ++ için stackoverflow.com/questions/105252/… ile aynı . Bunu daha net hale getirmek için düzenleme yapabiliriz.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Yanıtlar:


173

İhtiyacınız olan şeyin basit bir bayt değişimi olduğunu varsayarsak,

İmzasız 16 bit dönüştürme:

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

İmzasız 32 bit dönüştürme:

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

Bu, bayt emirlerini 1234 konumlarından 4321 konumuna değiştirir. Girişiniz öyleyse 0xdeadbeef, 32-bit endian takasın çıktısı olabilir 0xefbeadde.

Yukarıdaki kod, sihirli sayılar yerine makrolarla veya en azından sabitlerle temizlenmelidir, ancak umarım olduğu gibi yardımcı olur

DÜZENLEME: Başka bir cevabın işaret ettiği gibi, yukarıdakilerden ÇOK daha hızlı olabilen platform, işletim sistemi ve talimat setine özgü alternatifler vardır. Linux çekirdeğinde, endianness'ı oldukça güzel bir şekilde ele alan makrolar (örneğin cpu_to_be32) vardır. Ancak bu alternatifler çevrelerine özeldir. Pratikte bitkinlik en iyi şekilde mevcut yaklaşımların bir karışımı kullanılarak ele alınır


5
Platforma / donanıma özgü yöntemlerden bahsetmek için +1. Programlar her zaman bazı donanımlarda çalıştırılır ve donanım özellikleri her zaman en hızlıdır.
eonil

21
16 bitlik dönüştürme olarak yapılırsa ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 tek bir rolkomut oluşturur . Ve 32 bit dönüşüm olarak yazılırsa ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), aynı derleyici tek bir bswapkomut üretir .
user666412

Bunun ne kadar verimli olduğunu bilmiyorum ama bayt sırasını şu şekilde bit 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;}alanlarıyla değiştirdim: burası her biri 1 bit olan 8 alanlı bir bit alanıdır. Ancak bunun diğer öneriler kadar hızlı olup olmadığından emin değilim. İnts union { int i; byte_t[sizeof(int)]; }için tamsayıdaki bayt baytını ters çevirmek için kullanın .
Ilian Zapryanov

Sanırım ifade şöyle olmalı: (num >> 8) | (num << 8) bayt sırasını tersine çevirmek için DEĞİL: ((num & 0xff) >> 8) | (num << 8), Yanlış örnek düşük baytta sıfır alır.
jscom

@IlianZapryanov Açıklık için belki +1 olabilir, ancak C dilinde bit alanlarını bu şekilde kullanmak muhtemelen bunu yapmanın en az verimli yoludur.
sherrellbc

105

Dahil ederek:

#include <byteswap.h>

makineye bağlı bayt değiştirme işlevlerinin optimize edilmiş bir sürümünü edinebilirsiniz. Ardından aşağıdaki işlevleri kolayca kullanabilirsiniz:

__bswap_32 (uint32_t input)

veya

__bswap_16 (uint16_t input)

3
Cevabınız için teşekkürler, ancak herhangi bir kütüphane işlevini kullanamıyorum
Mark Ransom

4
Okumalısınız #include <byteswap.h>, .h dosyanın kendisinde yorumu görmek. Bu gönderi faydalı bilgiler içeriyor, bu yüzden yazara bir kitaplık işlevi kullanmama için OP gereksinimini göz ardı etmesine rağmen oy verdim.
Eli Rosencruft

30
Gerçekte, __bswap_32 / __ bswap_16 işlevleri makrolardır ve kütüphane işlevleri değildir, oylamanın başka bir nedeni.
Eli Rosencruft

7
Anladığım kadarıyla, bu başlığın tüm mimarilerdeki tüm işletim sistemleri için var olmasının garanti edilmediği. Henüz endian sorunlarıyla başa çıkmanın taşınabilir bir yolunu bulamadım.
Edward Falk

2
pencerelerde yok - en azından linux'tan mingw 32 veya 64 bit ile çapraz derleme yaparken değil
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);
}

Güncelleme : 64bit bayt takas eklendi

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

İçin int32_tve int64_tvaryant, maskeleme arkasındaki mantık nedir ... & 0xFFFFve ... & 0xFFFFFFFFULL? Burada işaret uzantısıyla ilgili görmediğim bir şeyler mi var? Ayrıca, neden swap_int64geri dönüyor uint64_t? Olması gerekmiyor int64_tmu?
bgoodr

1
Bir uint64 döndüren swap_int64 gerçekten bir hatadır. İşaretli int değerleriyle maskeleme, aslında işareti kaldırmak içindir. Sağa kaydırma, soldaki işaret bitini enjekte eder. İmzasız int takas işlemini çağırarak bunu önleyebiliriz.
chmike

Teşekkürler. swap_int64Cevabınızda dönüş değerinin türünü değiştirmek isteyebilirsiniz . Faydalı cevap için +1, BTW!
bgoodr

Bitsel ve değer endian bağımlı mı?
MarcusJ

1
LLGereksiz olan (u)swap_uint64()bir çok gibi Lgerekli değildir (u)swap_uint32(). Uİçinde gerekli değildir uswap_uint64()gibi pek Ude gerekli değildiruswap_uint32()
chux - Eski Monica

13

İşte oldukça genel bir versiyon; Derlemedim, bu yüzden büyük olasılıkla yazım hataları var, ama şunu anlamalısın,

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: Bu değil hız veya alanı için optimize. Açık (hata ayıklaması kolay) ve taşınabilir olması amaçlanmıştır.

Güncelleme 2018-04-04 yorumcu @chux tarafından tespit edildiği gibi geçersiz n == 0 durumunu yakalamak için assert () eklendi.


1
daha iyi performans için xorSwap'i kullanabilirsiniz. Tüm boyuta özgü olanlardan önce bu genel sürümü tercih edin ...

Test ettim, bunun x86'da xorSwap'ten daha hızlı olduğu ortaya çıktı. stackoverflow.com/questions/3128095/…

1
@nus - Çok basit kodun avantajlarından biri, derleyici optimizasyonunun bazen çok hızlı yapabilmesidir.
Michael J

@MichaelJ OTOH, chmike'ın cevabında yukarıdaki 32 bitlik sürüm bswap, optimizasyon etkinleştirilmiş iyi bir X86 derleyicisi tarafından tek bir talimatla derlenir . Boyut parametresi olan bu sürüm bunu yapamadı.
Alnitak

@Alnitak - Dediğim gibi, kodumu optimize etmek için hiçbir çaba sarf etmedim. Kullanıcı nus kodun çok hızlı çalıştığını fark ettiğinde (bir durumda) basit kodun genellikle bir derleyici tarafından yüksek oranda optimize edilebileceği genel fikrinden bahsetmiştim. Kodum çok çeşitli durumlarda işe yarıyor ve anlaşılması oldukça kolay ve dolayısıyla hata ayıklaması kolay. Bu benim hedeflerime ulaştı.
Michael J

9

Makrolara ihtiyacınız varsa (örn. Gömülü sistem):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))

Bu makrolar iyidir, ancak ((x) >> 24), işaretli bir tam sayı 0x80000000 ile 0xffffffff arasında olduğunda başarısız olur. Bitsel VE burada kullanmak iyi bir fikirdir. Not: ((x) << 24) tamamen güvenlidir. (x) >> 8), yüksek 16 bit sıfır değilse (veya işaretli 16 bitlik bir değer sağlanmışsa) da başarısız olacaktır.

2
@ PacMan - Bu makroların yalnızca işaretsiz tam sayıları takas etmek için kullanılması amaçlanmıştır . Bu yüzden UINTonların adında var.
kol

Evet, doğru, gürültü için üzgünüm. Bir typecast yerleştirmek en iyisi olmaz mıydı?

5

Düzenle: Bunlar kütüphane işlevleridir. Bunları takip etmek, bunu yapmanın manuel yoludur.

Farkında olmayan insanların sayısı beni kesinlikle şaşırttı __Byteswap_ushort, __byteswap_ulong ve __byteswap_uint64'ten şaşırttı . Elbette bunlar Visual C ++ 'ya özgüdür, ancak x86 / IA-64 mimarilerinde bazı lezzetli kodlara derleme yaparlar. :)

İşte bu sayfadan alınanbswap talimatın açık bir kullanımı . Yukarıdaki içsel formun her zaman bundan daha hızlı olacağına dikkat edin , sadece bir kütüphane rutini olmadan bir cevap vermek için ekledim.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}

21
Bir C sorusu için, Visual C ++ 'ya özgü bir şey mi öneriyorsunuz?
Alok Singhal

3
@Alok: Visual C ++ bir Microsoft ürünüdür. C kodunu derlemek için gayet iyi çalışıyor. :)
Sam Harwell

20
Microsoft'a özgü bayt kaydırma uygulamalarının pek çok kişinin farkında olmaması neden sizi şaşırtıyor?
dreamlax

36
Harika, bu taşınabilir veya standartlara uygun olması gerekmeyen kapalı kaynaklı bir ürün geliştiren herkes için iyi bir bilgi.
Sam Post

6
@Alok, OP derleyiciden | OS bahsetmedi. Bir kişinin belirli bir takım araçlarla deneyimine göre cevap vermesine izin verilir.
Aniket Inge

5

Şaka olarak:


#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? (Ne şakası?)

3
bunu bir Windows kaynak deposundan mı aldınız? :)
hochl

Nodejs bu tekniği kullanır! github.com/nodejs/node/blob/…
Justin Moser

int i, size_t sizeofIntHer ikisi için aynı tip değil, kullanımı meraklı .
chux - Monica'yı eski durumuna getir

5

4 saniyenin katlarına sahip olduğunuzu varsayarak, Intel özünü kullanarak SSSE3 komutu pshufb'u kullanmanın bir yolu int:

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

Bu işe yarayacak mı / daha hızlı olacak mı?

 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
Sanırım demek istediğin chardeğil byte.
dreamlax

Bu stratejiyi kullanarak, sizinkine kıyasla en çok oy alan çözüm eşdeğerdir ve en verimli ve taşınabilirdir. Ancak önerdiğim çözüm (en çok oy alan ikinci) daha az işlem gerektiriyor ve daha verimli olmalı.
chmike

1

İşte kullandığım bir işlev - test edilmiş ve herhangi bir temel veri türü üzerinde çalışıyor:

//  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
Kod çok makul bir varsayıma dayanır: sourcegerektiği gibi hizalanır - ancak bu varsayım geçerli değilse, kod UB'dir.
chux - Monica'yı eski haline getir

1

DÜZENLEME: Bu işlev yalnızca hizalanmış 16 bitlik kelimelerin sonluluğunu değiştirir. UTF-16 / UCS-2 kodlamaları için genellikle gerekli bir işlev. SONU DÜZENLE.

Bir bellek bloğunun dayanıklılığını değiştirmek istiyorsanız, benim son derece hızlı yaklaşımımı kullanabilirsiniz. Bellek dizinizin boyutu 8'in katı olmalıdır.

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

Bu tür bir işlev, Unicode UCS-2 / UTF-16 dosyalarının dayanıklılığını değiştirmek için kullanışlıdır.


Kodu tamamlamak için CHAR_BIT #define eksik.
Tõnu Samuel

Tamam, eksik içeriği ekledim.
Patrick Schlüter

burada C ++ 'da bir takas bağlantısı var t know if it, öneriler kadar hızlı değil ama uyandı: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov

CHAR_BITbağımlı 8olduğu gibi merak yerine . Sabit olarak gerekli olmadığını unutmayın . 0xFF00FF00FF00FF00ULLCHAR_BIT == 8LL
chux - Monica'yı eski durumuna getir

Haklısın chux. Sadece CHAR_BITbu makronun teşhirini artırmak için yazdı . LL'ye gelince, bu her şeyden çok bir açıklama. Aynı zamanda uzun zaman önce buggy derleyicilerle (standart öncesi) yakaladığım ve doğru şeyi yapmayan bir alışkanlık.
Patrick Schlüter

1

Bu kod parçacığı, 32 bitlik küçük Endian numarasını Büyük Endian numarasına dönüştürebilir.

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

Teşekkürler @ YuHao Burada yeniyim, Metni nasıl biçimlendireceğimi bilmiyorum.
Kaushal Billore

2
Kullanımı ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);daha hızlı bazı platformlarda olabilir (örn. VE maske sabitleri geri dönüşüm). Yine de çoğu derleyici bunu yapar, ancak bazı basit derleyiciler bunu sizin için optimize edemez.

-7

Bir x86 veya x86_64 işlemci üzerinde çalışıyorsanız, big endian yereldir. yani

16 bit değerler için

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

32 bit değerler için

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

Derleyici, bunun bayt seviyesi manipülasyonu olduğunu fark etmediği ve bayt takas kodu oluşturmadığı sürece bu en verimli çözüm değildir. Ancak herhangi bir bellek düzeni hilesine bağlı değildir ve oldukça kolay bir şekilde bir makroya dönüştürülebilir.


25
X86 ve x86_64 mimarilerinde küçük endian şeması yerel olanıdır.
MK aka Grisu
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.