私は自分の方法がリトルエンディアンからビッグエンディアンに変換するのに正しいかどうかを尋ねたいだけです。違いを理解しているかどうかを確認するだけです。
私はリトルエンディアンで保存されている番号を持っています、ここに番号のバイナリおよび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);
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);
関数htonl()
を使用できると思います。ネットワークバイトオーダーはビッグエンディアンです。
申し訳ありませんが、私の答えは少し遅すぎますが、パフォーマンスの観点から非常に重要なバイト順序を逆にする組み込み関数については誰も言及していません。
最新のプロセッサのほとんどはリトルエンディアンですが、すべてのネットワークプロトコルはビッグエンディアンです。これは歴史であり、ウィキペディアで を見つけることができます。 しかし、それは私たちのプロセッサがインターネットを閲覧している間にリトルエンディアンとビッグエンディアンの間で何百万回も変換することを意味します。
そのため、ほとんどのアーキテクチャには、このタスクを容易にする専用のプロセッサ命令があります。 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セント。
「各バイトを右にスワップしますか?」->はい、リトルエンディアンとビッグエンディアンの間で変換するには、バイトを逆順にするだけです。ただし、最初は次のことを理解してください。
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 ;
これを行うことができます:
int x = 0x12345678;
x = ( x >> 24 ) | (( x << 8) & 0x00ff0000 )| ((x >> 8) & 0x0000ff00) | ( x << 24) ;
printf("value = %x", x); // x will be printed as 0x78563412
私はあなたがLinux上にいると仮定しています
_"byteswap.h"
_を含めて、int32_t bswap_32(int32_t argument);
を使用します
それは論理的なビューです。実際には、_/usr/include/byteswap.h
_
<<
最後の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つは、16ビットまたは32ビットの値とcharの配列を結合することです。ビッグエンディアンの順序で受信されるシリアルメッセージを取得するときにこれを行ったばかりですが、リトルエンディアンのマイクロに取り組んでいます。
ユニオンMessageLengthUnion {
uint16_t asInt;
uint8_t asChars[2];
};
次に、メッセージを取得したら、最初に受け取ったuint8を.asChars [1]に、2番目を.asChars [0]に入れてから、残りのプログラムでユニオンの.asInt部分としてアクセスします。格納する32ビット値がある場合、配列の長さを4にすることができます。
小さなものから大きなものに変換する単純な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;
}
もう一つの提案:
unsigned int a = 0xABCDEF23;
a = ((a&(0x0000FFFF)) << 16) | ((a&(0xFFFF0000)) >> 16);
a = ((a&(0x00FF00FF)) << 8) | ((a&(0xFF00FF00)) >>8);
printf("%0x\n",a);
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);