web-dev-qa-db-ja.com

リトルエンディアンをビッグエンディアンに変換

私は自分の方法がリトルエンディアンからビッグエンディアンに変換するのに正しいかどうかを尋ねたいだけです。違いを理解しているかどうかを確認するだけです。

私はリトルエンディアンで保存されている番号を持っています、ここに番号のバイナリおよび16進表現があります:

‭0001 0010 0011 0100 0101 0110 0111 1000‬

‭12345678‬

ビッグエンディアン形式では、次のようにバイトを交換する必要があります。

1000 0111 0110 0101 0100 0011 0010 0001

‭87654321

これは正しいです?

また、以下のコードはこれを試みますが、失敗します。明らかに間違っているものはありますか、何かを最適化できますか?コードがこの変換に悪い場合は、その理由を説明して、同じ変換を実行するより良い方法を示してください。

uint32_t num = 0x12345678;
uint32_t b0,b1,b2,b3,b4,b5,b6,b7;
uint32_t res = 0;

b0 = (num & 0xf) << 28;
b1 = (num & 0xf0) << 24;
b2 = (num & 0xf00) << 20;
b3 = (num & 0xf000) << 16;
b4 = (num & 0xf0000) << 12;
b5 = (num & 0xf00000) << 8;
b6 = (num & 0xf000000) << 4;
b7 = (num & 0xf0000000) << 4;

res = b0 + b1 + b2 + b3 + b4 + b5 + b6 + b7;

printf("%d\n", res);
21
JeckyPorter

OPのサンプルコードが正しくありません。

エンディアン変換は、ビットおよび8ビットのバイトレベルで機能します。エンディアンの問題のほとんどは、バイトレベルを扱います。 OPコードは、4ビットニブルレベルでエンディアン変更を行っています。代わりに推奨:

// Swap endian (big to little) or (little to big)
uint32_t num = 9;
uint32_t b0,b1,b2,b3;
uint32_t res;

b0 = (num & 0x000000ff) << 24u;
b1 = (num & 0x0000ff00) << 8u;
b2 = (num & 0x00ff0000) >> 8u;
b3 = (num & 0xff000000) >> 24u;

res = b0 | b1 | b2 | b3;

printf("%" PRIX32 "\n", res);

パフォーマンスが本当に重要な場合、特定のプロセッサを知る必要があります。それ以外の場合は、コンパイラーに任せてください。

[編集] OPは物事を変更するコメントを追加しました。
「16進表現(st uv wx yz)で表される32ビット数値は、4バイトフィールドに(st uv wx yz)として記録されます。」

この場合、32ビット数のエンディアンはnknownであり、結果はlittleエンディアン順でメモリに保存する必要があります。

uint32_t num = 9;
uint8_t b[4];
b[0] = (uint8_t) (num >>  0u);
b[1] = (uint8_t) (num >>  8u);
b[2] = (uint8_t) (num >> 16u);
b[3] = (uint8_t) (num >> 24u);

[2016編集]簡素化

...結果の型は、昇格された左オペランドの型です。..ビット単位シフト演算子C11§6.5.73

shift定数(右側のオペランド)の後にuを使用すると、それがない場合と同じ結果になります。

b3 = (num & 0xff000000) >> 24u;
b[3] = (uint8_t) (num >> 24u);
// same as 
b3 = (num & 0xff000000) >> 24;
b[3] = (uint8_t) (num >> 24);
35
chux

関数htonl()を使用できると思います。ネットワークバイトオーダーはビッグエンディアンです。

23
aoak

申し訳ありませんが、私の答えは少し遅すぎますが、パフォーマンスの観点から非常に重要なバイト順序を逆にする組み込み関数については誰も言及していません。

最新のプロセッサのほとんどはリトルエンディアンですが、すべてのネットワークプロトコルはビッグエンディアンです。これは歴史であり、ウィキペディアで を見つけることができます。 しかし、それは私たちのプロセッサがインターネットを閲覧している間にリトルエンディアンとビッグエンディアンの間で何百万回も変換することを意味します。

そのため、ほとんどのアーキテクチャには、このタスクを容易にする専用のプロセッサ命令があります。 x86アーキテクチャにはBSWAP命令があり、ARMにはREVがあります。これは、バイト順を逆にする最も効率的な方法です。

Cコードでのアセンブリを回避するには、代わりにビルトインを使用できます。 GCCには__builtin_bswap32()関数があり、Visual C++には_byteswap_ulong()があります。これらの関数は、ほとんどのアーキテクチャでプロセッサ命令を1つだけ生成します。

以下に例を示します。

#include <stdio.h>
#include <inttypes.h>

int main()
{
    uint32_t le = 0x12345678;
    uint32_t be = __builtin_bswap32(le);

    printf("Little-endian: 0x%" PRIx32 "\n", le);
    printf("Big-endian:    0x%" PRIx32 "\n", be);

    return 0;
}

生成される出力は次のとおりです。

Little-endian: 0x12345678
Big-endian:    0x78563412

そして、これが逆アセンブリです(最適化なし、つまり-O0):

        uint32_t be = __builtin_bswap32(le);
   0x0000000000400535 <+15>:    mov    -0x8(%rbp),%eax
   0x0000000000400538 <+18>:    bswap  %eax
   0x000000000040053a <+20>:    mov    %eax,-0x4(%rbp)

BSWAP命令は1つだけです。

したがって、パフォーマンスを気にする場合は、代わりにこれらの組み込み関数を使用する必要がありますバイト反転の他の方法。ちょうど私の2セント。

16

「各バイトを右にスワップしますか?」->はい、リトルエンディアンとビッグエンディアンの間で変換するには、バイトを逆順にするだけです。ただし、最初は次のことを理解してください。

  • のサイズ uint32_tは32ビット、つまり4バイト、つまり8桁の16進数です
  • マスク 0xfは、最下位4ビットを取得します。8ビットを取得するには、0xff

そのため、4バイトの順序をその種のマスクと交換したい場合は、次のようにします。

uint32_t res = 0;
b0 = (num & 0xff) << 24;        ; least significant to most significant
b1 = (num & 0xff00) << 8;       ; 2nd least sig. to 2nd most sig.
b2 = (num & 0xff0000) >> 8;     ; 2nd most sig. to 2nd least sig.
b3 = (num & 0xff000000) >> 24;  ; most sig. to least sig.
res = b0 | b1 | b2 | b3 ;
8
LihO

これを行うことができます:

int x = 0x12345678;

x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24)  ; 

printf("value = %x", x);  // x will be printed as 0x78563412
4
ayyappa ch

私はあなたがLinux上にいると仮定しています

_"byteswap.h"_を含めて、int32_t bswap_32(int32_t argument);を使用します

それは論理的なビューです。実際には、_/usr/include/byteswap.h_

2
0x47-sci-tech

次の理由により、OPのコードは正しくありません。

  • スワップは、バイト(8ビット)境界ではなく、ニブル(4ビット)境界で実行されています。
  • 左シフト<<最後の4つのスワップの操作は正しくありません。右シフト>>操作とそのシフト値も修正する必要があります。
  • 中間ストレージの使用は不要であるため、コードをより簡潔/認識できるように書き換えることができます。そうすることで、一部のコンパイラーは、よく使用されるパターンを認識することで、コードをより最適化できるようになります。

符号なしの値を効率的に変換する次のコードを検討してください。

// Swap endian (big to little) or (little to big)
uint32_t num = 0x12345678;
uint32_t res =
    ((num & 0x000000FF) << 16) |
    ((num & 0x0000FF00) << 8) |
    ((num & 0x00FF0000) >> 8) |
    ((num & 0xFF000000) >> 16);

printf("%0x\n", res);

結果は、ここではバイナリと16進数の両方で表されます。バイトがどのようにスワップされたかに注意してください。

‭0111 1000 0101 0110 0011 0100 0001 0010‬

78563412

最適化

パフォーマンスの観点から、可能な場合はコードを最適化するためにコンパイラに任せてください。このような単純なアルゴリズムの配列のような不必要なデータ構造は避ける必要があります。そうすると、通常、CPUレジスタを使用する代わりにRAMにアクセスするなど、異なる命令動作が発生します。

1
Shaun Wilson

これに対処するための少し異なる方法の1つは、16ビットまたは32ビットの値とcharの配列を結合することです。ビッグエンディアンの順序で受信されるシリアルメッセージを取得するときにこれを行ったばかりですが、リトルエンディアンのマイクロに取り組んでいます。

ユニオンMessageLengthUnion {

uint16_t asInt;
uint8_t asChars[2];

};

次に、メッセージを取得したら、最初に受け取ったuint8を.asChars [1]に、2番目を.asChars [0]に入れてから、残りのプログラムでユニオンの.asInt部分としてアクセスします。格納する32ビット値がある場合、配列の長さを4にすることができます。

1
DiBosco

小さなものから大きなものに変換する単純なCプログラム

#include <stdio.h>

int main() {
unsigned int little=0x1234ABCD,big=0;
unsigned char tmp=0,l;

printf(" Little endian little=%x\n",little);

for(l=0;l < 4;l++) 
{
    tmp=0;
    tmp = little | tmp;
    big = tmp | (big << 8);
    little = little >> 8;
}
printf(" Big endian big=%x\n",big);

return 0;
}
1
Naresh pothula

もう一つの提案:

unsigned int a = 0xABCDEF23;
a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16);
a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8);
printf("%0x\n",a);
1
Saurabh Sengar

Lib関数を使用できます。それらはAssemblyに要約されますが、Cの代替実装を開いている場合は、ここにあります(intが32ビットであると仮定):

void byte_swap16(unsigned short int *pVal16) {

//#define method_one 1
// #define method_two 1
#define method_three 1
#ifdef method_one
    unsigned char *pByte;

    pByte = (unsigned char *) pVal16;
    *pVal16 = (pByte[0] << 8) | pByte[1];
#endif

#ifdef method_two
    unsigned char *pByte0;
    unsigned char *pByte1;

    pByte0 = (unsigned char *) pVal16;
    pByte1 = pByte0 + 1;
    *pByte0 = *pByte0 ^ *pByte1;
    *pByte1 = *pByte0 ^ *pByte1;
    *pByte0 = *pByte0 ^ *pByte1;
#endif

#ifdef method_three
    unsigned char *pByte;

    pByte = (unsigned char *) pVal16;
    pByte[0] = pByte[0] ^ pByte[1];
    pByte[1] = pByte[0] ^ pByte[1];
    pByte[0] = pByte[0] ^ pByte[1];
#endif


}



void byte_swap32(unsigned int *pVal32) {

#ifdef method_one
    unsigned char *pByte;

    // 0x1234 5678 --> 0x7856 3412  
    pByte = (unsigned char *) pVal32;
    *pVal32 = ( pByte[0] << 24 ) | (pByte[1] << 16) | (pByte[2] << 8) | ( pByte[3] );
#endif

#if defined(method_two) || defined (method_three)
    unsigned char *pByte;

    pByte = (unsigned char *) pVal32;
    // move lsb to msb
    pByte[0] = pByte[0] ^ pByte[3];
    pByte[3] = pByte[0] ^ pByte[3];
    pByte[0] = pByte[0] ^ pByte[3];
    // move lsb to msb
    pByte[1] = pByte[1] ^ pByte[2];
    pByte[2] = pByte[1] ^ pByte[2];
    pByte[1] = pByte[1] ^ pByte[2];
#endif
}

そして、使用方法は次のように実行されます。

unsigned short int u16Val = 0x1234;
byte_swap16(&u16Val);
unsigned int u32Val = 0x12345678;
byte_swap32(&u32Val);
0
netskink