C++ でビッグエンディアン値とリトルエンディアン値の間で変換するにはどうすればよいですか?
-
01-07-2019 - |
質問
C++ でビッグエンディアン値とリトルエンディアン値の間で変換するにはどうすればよいですか?
編集:わかりやすくするために、バイナリ データ (倍精度浮動小数点値、32 ビットおよび 64 ビット整数) をある CPU アーキテクチャから別の CPU アーキテクチャに変換する必要があります。これにはネットワークが関与しないため、ntoh() および同様の関数はここでは機能しません。
編集#2:私が受け入れた答えは、私が対象としているコンパイラに直接当てはまります (それが私がそれを選んだ理由です)。ただし、他にも非常に優れた、より移植性の高い答えがここにあります。
解決
使用している場合 ビジュアルC++ 以下をせよ:intrin.h をインクルードし、次の関数を呼び出します。
16 ビット数値の場合:
unsigned short _byteswap_ushort(unsigned short value);
32 ビット数値の場合:
unsigned long _byteswap_ulong(unsigned long value);
64 ビット数値の場合:
unsigned __int64 _byteswap_uint64(unsigned __int64 value);
8 ビット数値 (文字) は変換する必要はありません。
また、これらは符号なしの値に対してのみ定義されており、符号付き整数に対しても同様に機能します。
float と double の場合は、ホスト マシンのバイト オーダーにあるかどうかにかかわらず、単純な整数の場合と同様に困難です。ビッグエンディアンマシン上でリトルエンディアン浮動小数点を取得することも、その逆も可能です。
他のコンパイラにも同様の組み込み関数があります。
で GCC たとえば、次のように直接呼び出すことができます。
int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)
(何かを含める必要はありません)。私の知っているbits.hは、gcc中心ではない方法でも同じ関数を宣言しています。
16 ビット スワップは、ビットを回転するだけです。
独自の組み込み関数を呼び出すのではなく、組み込み関数を呼び出すと、最高のパフォーマンスとコード密度が得られます。
他のヒント
簡単に言えば:
#include <climits>
template <typename T>
T swap_endian(T u)
{
static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");
union
{
T u;
unsigned char u8[sizeof(T)];
} source, dest;
source.u = u;
for (size_t k = 0; k < sizeof(T); k++)
dest.u8[k] = source.u8[sizeof(T) - k - 1];
return dest.u;
}
使用法: swap_endian<uint32_t>(42)
.
から バイトオーダーの誤謬 ロブ・パイク著:
データ ストリームにリトル エンディアンでエンコードされた 32 ビット整数があるとします。これを抽出する方法は次のとおりです (符号なしバイトを想定)。
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
ビッグエンディアンの場合、それを抽出する方法は次のとおりです。
i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);
TL;DR: プラットフォームのネイティブ順序については心配する必要はありません。重要なのは、読み取り元のストリームのバイト順序だけです。それが明確に定義されていることを願ったほうがよいでしょう。
注記:コメントでは、明示的な型変換がないことが重要であると述べられていました。 data
の配列になる unsigned char
または uint8_t
. 。使用する signed char
または char
(署名されている場合) は次のようになります data[x]
整数に昇格され、 data[x] << 24
UB である符号ビットに 1 をシフトする可能性があります。
ネットワーク/ホストの互換性を目的としてこれを行う場合は、次のものを使用する必要があります。
ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)
ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)
他の理由でこれを実行している場合は、ここで紹介する byte_swap ソリューションのいずれかが問題なく機能します。
この投稿からいくつかの提案を取り出し、それらをまとめてこれを作成しました。
#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>
enum endianness
{
little_endian,
big_endian,
network_endian = big_endian,
#if defined(BOOST_LITTLE_ENDIAN)
host_endian = little_endian
#elif defined(BOOST_BIG_ENDIAN)
host_endian = big_endian
#else
#error "unable to determine system endianness"
#endif
};
namespace detail {
template<typename T, size_t sz>
struct swap_bytes
{
inline T operator()(T val)
{
throw std::out_of_range("data size");
}
};
template<typename T>
struct swap_bytes<T, 1>
{
inline T operator()(T val)
{
return val;
}
};
template<typename T>
struct swap_bytes<T, 2>
{
inline T operator()(T val)
{
return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
}
};
template<typename T>
struct swap_bytes<T, 4>
{
inline T operator()(T val)
{
return ((((val) & 0xff000000) >> 24) |
(((val) & 0x00ff0000) >> 8) |
(((val) & 0x0000ff00) << 8) |
(((val) & 0x000000ff) << 24));
}
};
template<>
struct swap_bytes<float, 4>
{
inline float operator()(float val)
{
uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
return *(float*)&mem;
}
};
template<typename T>
struct swap_bytes<T, 8>
{
inline T operator()(T val)
{
return ((((val) & 0xff00000000000000ull) >> 56) |
(((val) & 0x00ff000000000000ull) >> 40) |
(((val) & 0x0000ff0000000000ull) >> 24) |
(((val) & 0x000000ff00000000ull) >> 8 ) |
(((val) & 0x00000000ff000000ull) << 8 ) |
(((val) & 0x0000000000ff0000ull) << 24) |
(((val) & 0x000000000000ff00ull) << 40) |
(((val) & 0x00000000000000ffull) << 56));
}
};
template<>
struct swap_bytes<double, 8>
{
inline double operator()(double val)
{
uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
return *(double*)&mem;
}
};
template<endianness from, endianness to, class T>
struct do_byte_swap
{
inline T operator()(T value)
{
return swap_bytes<T, sizeof(T)>()(value);
}
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian, big_endian, T> { inline T operator()(T value) { return value; } };
} // namespace detail
template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
// ensure the data is only 1, 2, 4 or 8 bytes
BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
// ensure we're only swapping arithmetic types
BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);
return detail::do_byte_swap<from, to, T>()(value);
}
スワップを実行してくれる BSWAP というアセンブリ命令があります。 非常に速い。それについて読むことができます ここ.
Visual Studio、より正確には Visual C++ ランタイム ライブラリには、このためのプラットフォーム組み込み関数があります。 _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64()
. 。他のプラットフォームにも同様のものが存在するはずですが、それが何と呼ばれるかはわかりません。
ビッグエンディアンからリトルエンディアンに移行する手順は、リトルエンディアンからビッグエンディアンに移行する手順と同じです。
コード例を次に示します。
void swapByteOrder(unsigned short& us)
{
us = (us >> 8) |
(us << 8);
}
void swapByteOrder(unsigned int& ui)
{
ui = (ui >> 24) |
((ui<<8) & 0x00FF0000) |
((ui>>8) & 0x0000FF00) |
(ui << 24);
}
void swapByteOrder(unsigned long long& ull)
{
ull = (ull >> 56) |
((ull<<40) & 0x00FF000000000000) |
((ull<<24) & 0x0000FF0000000000) |
((ull<<8) & 0x000000FF00000000) |
((ull>>8) & 0x00000000FF000000) |
((ull>>24) & 0x0000000000FF0000) |
((ull>>40) & 0x000000000000FF00) |
(ull << 56);
}
これはテンプレートを使用して行いました。次のようなことができます:
// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
// Use bit manipulations instead of accessing individual bytes from memory, much faster.
ushort* p_dest = reinterpret_cast< ushort* >(dest);
ushort const* const p_src = reinterpret_cast< ushort const* >(src);
*p_dest = (*p_src >> 8) | (*p_src << 8);
}
// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
// Use bit manipulations instead of accessing individual bytes from memory, much faster.
uint* p_dest = reinterpret_cast< uint* >(dest);
uint const* const p_src = reinterpret_cast< uint const* >(src);
*p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}
異なるプラットフォーム間でデータを転送するためにこれを行っている場合は、ntoh 関数と hton 関数を見てください。
C で行うのと同じ方法:
short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));
unsigned char のベクトルを宣言し、そのベクトルに入力値を memcpy で入れ、バイトを別のベクトルに反転して、バイトを memcpy で出力することもできますが、これは、特に 64 ビット値の場合、ビットいじりよりも桁違いに時間がかかります。
ほとんどの POSIX システムには (POSIX 標準には含まれていませんが)、システムが使用するエンコーディングを決定するために使用できる endian.h があります。そこからは次のようなものです。
unsigned int change_endian(unsinged int x)
{
unsigned char *ptr = (unsigned char *)&x;
return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}
これにより、順序が (ビッグエンディアンからリトルエンディアンに) 入れ替えられます。
数値が 0xDEADBEEF (リトル エンディアン システムでは 0xEFBEADDE として保存される) の場合、ptr[0] は 0xEF、ptr[1] は 0xBE になります。
ただし、ネットワークに使用したい場合は、htons、htonl、htonll (およびその逆の ntohs、ntohl、ntohll) がホストの順序からネットワークの順序に変換するのに役立ちます。
少なくとも Windows の場合、htonl() は、対応する組み込みの _byteswap_ulong() よりもはるかに遅いことに注意してください。前者は ws2_32.dll への DLL ライブラリ呼び出しであり、後者は 1 つの BSWAP アセンブリ命令です。したがって、プラットフォームに依存するコードを作成している場合は、速度を上げるために組み込み関数を使用することをお勧めします。
#define htonl(x) _byteswap_ulong(x)
これは、すべての整数がビッグ エンディアンで保存される .PNG 画像処理で特に重要になる可能性があります。「準備ができていない場合は、一般的な Windows プログラムの速度を低下させるために htonl()... を使用できます」という説明が付けられます。
ほとんどのプラットフォームには、効率的なバイトスワップ機能を提供するシステム ヘッダー ファイルがあります。Linux では、 <endian.h>
. 。C++ でうまくラップできます。
#include <iostream>
#include <endian.h>
template<size_t N> struct SizeT {};
#define BYTESWAPS(bits) \
template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }
BYTESWAPS(16)
BYTESWAPS(32)
BYTESWAPS(64)
#undef BYTESWAPS
template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }
int main()
{
std::cout << std::hex;
std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
std::cout << htobe(0xafbeadde) << '\n';
// Use ULL suffix to specify integer constant as unsigned long long
std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
}
出力:
cafe
deadbeaf
feeddeafbeefcafe
私はこれが好きです、ただスタイルのために :-)
long swap(long i) {
char *c = (char *) &i;
return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}
真剣に...なぜすべての解決策がそうなるのか理解できません 複雑! あらゆるオペレーティング システムのあらゆる状況下で、あらゆるタイプ、あらゆるサイズを交換する、最も単純で最も一般的なテンプレート関数はどうでしょうか????
template <typename T>
void SwapEnd(T& var)
{
char* varArray = reinterpret_cast<char*>(&var);
for(long i = 0; i < static_cast<long>(sizeof(var)/2); i++)
std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
}
これは C と C++ の魔法のような力です。元の変数を 1 文字ずつ交換するだけです。
エンディアンを反転すると一部のオブジェクトが混乱し、コピー コンストラクター (または代入演算子) が機能しなくなるため、単純な代入演算子「=」を使用しなかったことを思い出してください。したがって、文字ごとにコピーする方が確実です。
それを呼び出すには、単に使用します
double x = 5;
SwapEnd(x);
そしていま x
エンディアンネスが異なります。
このコードを使用すると、HOST_ENDIAN_ORDER (それが何であっても) から LITTLE_ENDIAN_ORDER または BIG_ENDIAN_ORDER に変換できます。テンプレートを使用しているため、HOST_ENDIAN_ORDER から LITTLE_ENDIAN_ORDER に変換しようとして、コンパイルするマシンでそれらがたまたま同じである場合、コードは生成されません。
以下にいくつかのコメントを含むコードを示します。
// We define some constant for little, big and host endianess. Here I use
// BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
// don't want to use boost you will have to modify this part a bit.
enum EEndian
{
LITTLE_ENDIAN_ORDER,
BIG_ENDIAN_ORDER,
#if defined(BOOST_LITTLE_ENDIAN)
HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
#elif defined(BOOST_BIG_ENDIAN)
HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
#else
#error "Impossible de determiner l'indianness du systeme cible."
#endif
};
// this function swap the bytes of values given it's size as a template
// parameter (could sizeof be used?).
template <class T, unsigned int size>
inline T SwapBytes(T value)
{
union
{
T value;
char bytes[size];
} in, out;
in.value = value;
for (unsigned int i = 0; i < size / 2; ++i)
{
out.bytes[i] = in.bytes[size - 1 - i];
out.bytes[size - 1 - i] = in.bytes[i];
}
return out.value;
}
// Here is the function you will use. Again there is two compile-time assertion
// that use the boost librarie. You could probably comment them out, but if you
// do be cautious not to use this function for anything else than integers
// types. This function need to be calles like this :
//
// int x = someValue;
// int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
//
template<EEndian from, EEndian to, class T>
inline T EndianSwapBytes(T value)
{
// A : La donnée à swapper à une taille de 2, 4 ou 8 octets
BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
// A : La donnée à swapper est d'un type arithmetic
BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);
// Si from et to sont du même type on ne swap pas.
if (from == to)
return value;
return SwapBytes<T, sizeof(T)>(value);
}
ビッグエンディアンの 32 ビット符号なし整数が 0xAABBCCDD (2864434397 に等しい) のように見える場合、その同じ 32 ビット符号なし整数は、リトル エンディアン プロセッサ上では 0xDDCCBBAA (同じく 2864434397 に等しい) のように見えます。
ビッグ エンディアンの 16 ビット符号なし short が 0xAABB (43707 に等しい) のように見える場合、その同じ 16 ビット符号なし short は、リトル エンディアン プロセッサ上では 0xBBAA (同じく 43707 に等しい) のように見えます。
ここでは、リトルエンディアンからビッグエンディアンへ、またはその逆にバイトを交換するための便利な #define 関数をいくつか紹介します -->
// can be used for short, unsigned short, word, unsigned word (2-byte types)
#define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))
// can be used for int or unsigned int or float (4-byte types)
#define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))
// can be used for unsigned long long or double (8-byte types)
#define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))
これは、値を適切に交換するために私が思いついた一般化したバージョンです。パフォーマンスに問題がある場合は、他の提案の方が適しています。
template<typename T>
void ByteSwap(T * p)
{
for (int i = 0; i < sizeof(T)/2; ++i)
std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
}
免責事項: まだこれをコンパイルしたりテストしたりしていません。
ワード内のビットの順序を反転する一般的なパターンを採用し、各バイト内のビットを反転する部分を削除すると、ワード内のバイトのみを反転するものが残ります。64 ビットの場合:
x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) << 8) ^ ((x >> 8) & 0x00ff00ff00ff00ff);
コンパイラ すべき 余分なビットマスキング操作を削除します (パターンを強調表示するために残しました)。そうでない場合は、最初の行を次のように書き換えることができます。
x = ( x << 32) ^ (x >> 32);
これは通常、ほとんどのアーキテクチャで単一の回転命令にまで単純化されるはずです (操作全体がおそらく 1 つの命令であることを無視します)。
RISC プロセッサでは、大きくて複雑な定数によってコンパイラに問題が発生する可能性があります。ただし、前の定数から各定数を簡単に計算できます。そのようです:
uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) << 8) ^ ((x >> 8) & k);
必要に応じて、それをループとして書くことができます。効率的ではありませんが、ただ楽しむために:
int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
x = ((x & k) << i) ^ ((x >> i) & k);
i >>= 1;
k ^= k << i;
}
完全を期すために、最初の形式の簡略化された 32 ビット バージョンを次に示します。
x = ( x << 16) ^ (x >> 16);
x = ((x & 0x00ff00ff) << 8) ^ ((x >> 8) & 0x00ff00ff);
どこにも見つからなかったので、独自のソリューションをここに追加したと思いました。これは小さくて移植可能な C++ テンプレート関数であり、ビット演算のみを使用する移植性があります。
template<typename T> inline static T swapByteOrder(const T& val) {
int totalBytes = sizeof(val);
T swapped = (T) 0;
for (int i = 0; i < totalBytes; ++i) {
swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
}
return swapped;
}
以下のコードを使用すると、BigEndian と LittleEndian を簡単に切り替えることができます。
#define uint32_t unsigned
#define uint16_t unsigned short
#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))
#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))
htobeXX と betohXX 関数について誰も言及しなかったのには本当に驚きました。これらは endian.h で定義されており、ネットワーク関数 htonXX に非常に似ています。
うわー、ここで読んだ答えのいくつかは信じられませんでした。実際、アセンブリにはこれを他の何よりも速く行う命令があります。バスワップ。このような関数を単純に書くこともできます...
__declspec(naked) uint32_t EndianSwap(uint32 value)
{
__asm
{
mov eax, dword ptr[esp + 4]
bswap eax
ret
}
}
それは 多くの 提案されている組み込みよりも高速です。分解して見てみました。上記の関数にはプロローグ/エピローグがないため、実質的にオーバーヘッドはまったくありません。
unsigned long _byteswap_ulong(unsigned long value);
16 ビットも同様に簡単ですが、xchg al を使用する点が異なります。bswap は 32 ビット レジスタでのみ機能します。
64 ビットは少し注意が必要ですが、それほど難しいわけではありません。ループやテンプレートなどを使用した上記のすべての例よりもはるかに優れています。
ここでいくつか注意点があります...まず、bswap は 80x486 CPU 以上でのみ使用できます。386で実行する予定の人はいますか?その場合でも、bswap を次のように置き換えることができます。
mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx
また、インライン アセンブリは、Visual Studio の x86 コードでのみ使用できます。ネイキッド関数はライニングできず、x64 ビルドでも使用できません。その場合、コンパイラ組み込み関数を使用する必要があります。
オプティマイザに適した非整列非インプレース エンディアン アクセサーを実装するための移植可能な手法。これらは、すべてのコンパイラ、すべての境界アライメント、およびすべてのバイト順序で機能します。これらのアライメントされていないルーチンは、ネイティブ エンディアンとアライメントに応じて補足または無効になります。部分的なリストですが、アイデアはわかります。BO* は、ネイティブのバイト順序に基づく定数値です。
uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
union {
uint32_1234 u32_1234;
uint32_t u32;
} bou32;
bou32.u32_1234[0] = (*pu32)[BO32_0];
bou32.u32_1234[1] = (*pu32)[BO32_1];
bou32.u32_1234[2] = (*pu32)[BO32_2];
bou32.u32_1234[3] = (*pu32)[BO32_3];
return(bou32.u32);
}
void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
union {
uint32_1234 u32_1234;
uint32_t u32;
} bou32;
bou32.u32 = u32;
(*pu32)[BO32_0] = bou32.u32_1234[0];
(*pu32)[BO32_1] = bou32.u32_1234[1];
(*pu32)[BO32_2] = bou32.u32_1234[2];
(*pu32)[BO32_3] = bou32.u32_1234[3];
}
#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
union {
int64_12345678 i64_12345678;
int64 i64;
} boi64;
boi64.i64_12345678[0] = (*pi64)[BO64_0];
boi64.i64_12345678[1] = (*pi64)[BO64_1];
boi64.i64_12345678[2] = (*pi64)[BO64_2];
boi64.i64_12345678[3] = (*pi64)[BO64_3];
boi64.i64_12345678[4] = (*pi64)[BO64_4];
boi64.i64_12345678[5] = (*pi64)[BO64_5];
boi64.i64_12345678[6] = (*pi64)[BO64_6];
boi64.i64_12345678[7] = (*pi64)[BO64_7];
return(boi64.i64);
}
#endif
int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
union {
int32_3412 i32_3412;
int32_t i32;
} boi32;
boi32.i32_3412[2] = (*pi32)[BO32_0];
boi32.i32_3412[3] = (*pi32)[BO32_1];
boi32.i32_3412[0] = (*pi32)[BO32_2];
boi32.i32_3412[1] = (*pi32)[BO32_3];
return(boi32.i32);
}
void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
union {
int32_3412 i32_3412;
int32_t i32;
} boi32;
boi32.i32 = i32;
(*pi32)[BO32_0] = boi32.i32_3412[2];
(*pi32)[BO32_1] = boi32.i32_3412[3];
(*pi32)[BO32_2] = boi32.i32_3412[0];
(*pi32)[BO32_3] = boi32.i32_3412[1];
}
uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
union {
uint32_3412 u32_3412;
uint32_t u32;
} bou32;
bou32.u32_3412[2] = (*pu32)[BO32_0];
bou32.u32_3412[3] = (*pu32)[BO32_1];
bou32.u32_3412[0] = (*pu32)[BO32_2];
bou32.u32_3412[1] = (*pu32)[BO32_3];
return(bou32.u32);
}
void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
union {
uint32_3412 u32_3412;
uint32_t u32;
} bou32;
bou32.u32 = u32;
(*pu32)[BO32_0] = bou32.u32_3412[2];
(*pu32)[BO32_1] = bou32.u32_3412[3];
(*pu32)[BO32_2] = bou32.u32_3412[0];
(*pu32)[BO32_3] = bou32.u32_3412[1];
}
float sw_get_float_1234(pf)
float_1234 *pf;
{
union {
float_1234 f_1234;
float f;
} bof;
bof.f_1234[0] = (*pf)[BO32_0];
bof.f_1234[1] = (*pf)[BO32_1];
bof.f_1234[2] = (*pf)[BO32_2];
bof.f_1234[3] = (*pf)[BO32_3];
return(bof.f);
}
void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
union {
float_1234 f_1234;
float f;
} bof;
bof.f = (float)f;
(*pf)[BO32_0] = bof.f_1234[0];
(*pf)[BO32_1] = bof.f_1234[1];
(*pf)[BO32_2] = bof.f_1234[2];
(*pf)[BO32_3] = bof.f_1234[3];
}
double sw_get_double_12345678(pd)
double_12345678 *pd;
{
union {
double_12345678 d_12345678;
double d;
} bod;
bod.d_12345678[0] = (*pd)[BO64_0];
bod.d_12345678[1] = (*pd)[BO64_1];
bod.d_12345678[2] = (*pd)[BO64_2];
bod.d_12345678[3] = (*pd)[BO64_3];
bod.d_12345678[4] = (*pd)[BO64_4];
bod.d_12345678[5] = (*pd)[BO64_5];
bod.d_12345678[6] = (*pd)[BO64_6];
bod.d_12345678[7] = (*pd)[BO64_7];
return(bod.d);
}
void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
union {
double_12345678 d_12345678;
double d;
} bod;
bod.d = d;
(*pd)[BO64_0] = bod.d_12345678[0];
(*pd)[BO64_1] = bod.d_12345678[1];
(*pd)[BO64_2] = bod.d_12345678[2];
(*pd)[BO64_3] = bod.d_12345678[3];
(*pd)[BO64_4] = bod.d_12345678[4];
(*pd)[BO64_5] = bod.d_12345678[5];
(*pd)[BO64_6] = bod.d_12345678[6];
(*pd)[BO64_7] = bod.d_12345678[7];
}
これらの typedef には、アクセサーで使用しない場合にコンパイラ エラーが発生するという利点があり、アクセサーの忘れられたバグが軽減されます。
typedef char int8_1[1], uint8_1[1];
typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */
typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */
typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */
typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */
typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */
typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */
最近、これを行うマクロを C で書きましたが、これは C++ でも同様に有効です。
#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)
任意の型を受け入れ、渡された引数のバイトを反転します。使用例:
int main(){
unsigned long long x = 0xABCDEF0123456789;
printf("Before: %llX\n",x);
REVERSE_BYTES(x);
printf("After : %llX\n",x);
char c[7]="nametag";
printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
REVERSE_BYTES(c);
printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}
印刷内容:
Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman
上記は完全にコピー/ペーストできますが、ここでは多くのことが行われているため、それがどのように機能するかを部分的に説明します。
最初に注目すべき点は、マクロ全体が do while(0)
ブロック。これは 一般的な慣用句 マクロの後に通常のセミコロンを使用できるようにします。
次に、という名前の変数を使用します。 REVERSE_BYTES
として for
ループのカウンター。マクロ自体の名前は、マクロが使用される場所のスコープ内にある他のシンボルと衝突しないようにするために、変数名として使用されます。この名前はマクロの展開内で使用されているため、ここで変数名として使用しても再度展開されることはありません。
以内 for
ループでは 2 バイトが参照されており、 XORが交換されました (したがって、一時変数名は必要ありません):
((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]
__VA_ARGS__
はマクロに与えられたものをすべて表し、渡されるものの柔軟性を高めるために使用されます(それほどではありませんが)。次に、この引数のアドレスが取得され、 unsigned char
配列を介したバイトの交換を許可するポインタ []
添え字をつけること。
最後の特異な点は、 {}
ブレース。各スワップのすべてのステップが結合されるため、これらは必要ありません。 カンマ演算子, 、それらを1つのステートメントにします。
最後に、速度が最優先の場合、これは理想的なアプローチではないことに注意してください。これが重要な要素である場合は、他の回答で参照されているタイプ固有のマクロまたはプラットフォーム固有のディレクティブのいくつかがより良いオプションである可能性があります。ただし、このアプローチは、すべての型、すべての主要なプラットフォーム、および C 言語と C++ 言語の両方に移植可能です。
試す Boost::endian
, 自分で実装しないでください。
ここにあります リンク
ホスト コンピュータが別のシステムを使用している場合でも、IEEE 754 64 ビット形式で保存された double を読み取る方法は次のとおりです。
/*
* read a double from a stream in ieee754 format regardless of host
* encoding.
* fp - the stream
* bigendian - set to if big bytes first, clear for little bytes
* first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
unsigned char buff[8];
int i;
double fnorm = 0.0;
unsigned char temp;
int sign;
int exponent;
double bitval;
int maski, mask;
int expbits = 11;
int significandbits = 52;
int shift;
double answer;
/* read the data */
for (i = 0; i < 8; i++)
buff[i] = fgetc(fp);
/* just reverse if not big-endian*/
if (!bigendian)
{
for (i = 0; i < 4; i++)
{
temp = buff[i];
buff[i] = buff[8 - i - 1];
buff[8 - i - 1] = temp;
}
}
sign = buff[0] & 0x80 ? -1 : 1;
/* exponet in raw format*/
exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);
/* read inthe mantissa. Top bit is 0.5, the successive bits half*/
bitval = 0.5;
maski = 1;
mask = 0x08;
for (i = 0; i < significandbits; i++)
{
if (buff[maski] & mask)
fnorm += bitval;
bitval /= 2.0;
mask >>= 1;
if (mask == 0)
{
mask = 0x80;
maski++;
}
}
/* handle zero specially */
if (exponent == 0 && fnorm == 0)
return 0.0;
shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
/* nans have exp 1024 and non-zero mantissa */
if (shift == 1024 && fnorm != 0)
return sqrt(-1.0);
/*infinity*/
if (shift == 1024 && fnorm == 0)
{
#ifdef INFINITY
return sign == 1 ? INFINITY : -INFINITY;
#endif
return (sign * 1.0) / 0.0;
}
if (shift > -1023)
{
answer = ldexp(fnorm + 1.0, shift);
return answer * sign;
}
else
{
/* denormalised numbers */
if (fnorm == 0.0)
return 0.0;
shift = -1022;
while (fnorm < 1.0)
{
fnorm *= 2;
shift--;
}
answer = ldexp(fnorm, shift);
return answer * sign;
}
}
書き込みルーチンや整数ルーチンを含む残りの関数スイートについては、私の github プロジェクトを参照してください。
テンプレート関数のピボットを中心とした古い 3 ステップ XOR トリックを使用したバイト スワッピングは、ライブラリを必要としない柔軟で迅速な O(ln2) ソリューションを提供します。ここでのスタイルは 1 バイト型も拒否します。
template<typename T>void swap(T &t){
for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
*((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
*((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
*((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
}
}
安全な方法は、各単語に hton を使用することのようです。それで、もしあなたが持っているなら...
std::vector<uint16_t> storage(n); // where n is the number to be converted
// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
, word_storage.begin(), [](const uint16_t input)->uint16_t {
return htons(input); });
ビッグエンディアン システムを使用している場合、上記はノーオペレーションになるため、htons がノーオペレーションであるかどうかを判断するために、プラットフォームがコンパイル時の条件として使用しているものを探します。やっぱりO(n)なんですね。Mac では、次のようになります...
#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
, word_storage.begin(), [](const uint16_t input)->uint16_t {
return htons(input); });
#endif
基本的にリトルエンディアンからビッグエンディアンへの交換に必要なのはこれだけなので、ビットシフトを調べてください。次に、ビット サイズに応じて、ビット シフトの方法を変更します。