blog をもう少しコンスタントに更新して行きたいなぁと言う事で,メモ代わり感覚で更新していきます.
http://sourceforge.jp/tracker/index.php?func=detail&aid=15209&group_id=3652&atid=13543 の指摘を受けて.これに関わらず,多バイト長の変数(or その配列)から1バイトの配列へのコピー(あるいはその逆)を行う機会にしばしば遭遇するので,この機会に作成しておきます.最も,私の実行環境が全てリトルエンディアンなので,まともにテストできていないのですが・・・
エンディアンの判別
エンディアンの判別に関しては,http://www.math.kobe-u.ac.jp/~kodama/tips-C-endian.html で紹介されている方法を使用する事にします.具体的な方法は,__LITTLE_ENDIAN または,__BIG_ENDIAN が定義されている場合はその定義に従い,どちらも定義されていない場合は,int 型の 1 を char 型にキャストして判別すると言うものです.尚,PDP エンディアンなどは想定していません.
追記
リンク先の記事を読み間違えていたようです.コメントでも指摘がありましたが,
- Linux の gcc の場合,endian.h において __LITTLE_ENDIAN と __BIG_ENDIAN が両方とも(さらに __PDP_ENDIAN も?)定義されている.さらに,__BYTE_ORDER が定義されており,この値が __LITTLE_ENDIAN, __BIG_ENDIAN のいずれかの値を取る.
- PowerPC の場合は,__BIG_ENDIAN__(サフィックスが付いている)が定義されている.
と言う形のようです.定義名も PC 毎に細かな違いがある(サフィックスの有無等)ようなので,判定方法としては int の 1 を char にキャストした結果を用いる方法を採用しようと思います.
namespace clx { namespace endian { enum { big = 0, little }; /* ----------------------------------------------------------------- */ // which /* ----------------------------------------------------------------- */ int which() { //#if defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) //#if defined(__BYTE_ORDER) && (__BYTE_ORDER == __LITTLE_ENDIAN) // return little; //#elif defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN) // return big; //#endif //#elif defined(__LITTLE_ENDIAN) // return little; //#elif defined(__BIG_ENDIAN) // return big; //#endif int x = 0x00000001; if (*(char*)&x) return little; else return big; } /* ----------------------------------------------------------------- */ // is_little /* ----------------------------------------------------------------- */ bool is_little() { return which() == little; } /* ----------------------------------------------------------------- */ // is_big /* ----------------------------------------------------------------- */ bool is_big() { return which() == big; } } }
バイト列の反転
次に,バイト列の反転を行うための関数を作成します.どうしようか少し悩んだのですが,素直に std::reverse() を利用する事とします.
namespace clx { /* --------------------------------------------------------------------- */ // reverse /* --------------------------------------------------------------------- */ inline void reverse(unsigned char* src, size_t n) { std::reverse(src, src + n); } /* --------------------------------------------------------------------- */ // reverse /* --------------------------------------------------------------------- */ template <class Type> inline Type& reverse(Type& src) { if (sizeof(Type) == 1) return src; reverse(reinterpret_cast<unsigned char*>(&src), sizeof(Type)); return src; } /* --------------------------------------------------------------------- */ // reverse_copy /* --------------------------------------------------------------------- */ template <class Type> inline Type reverse_copy(const Type& src) { Type dest = src; return reverse(dest); } }
メモリコピー
これまでに作成した関数を利用して,エンディアンを考慮した memcpy を定義します.今回は,多バイト長の配列 -> 1 バイトの配列,および 1 バイトの配列 -> 多バイト長の配列の memcpy のみを定義します(多バイト長配列から多バイト長配列へのコピーを行おうとするとコンパイルエラーとなる).
namespace clx { /* --------------------------------------------------------------------- */ // memcpy /* --------------------------------------------------------------------- */ inline unsigned char* memcpy(unsigned char* dest, const unsigned char* src, int bytes) { std::memcpy(dest, src, bytes); return dest; } inline char* memcpy(char* dest, const char* src, int bytes) { std::memcpy(dest, src, bytes); return dest; } /* --------------------------------------------------------------------- */ // memcpy /* --------------------------------------------------------------------- */ template <class Type> inline unsigned char* memcpy(unsigned char* dest, const Type* src, int bytes) { if (endian::is_little()) { std::memcpy(dest, reinterpret_cast<const unsigned char*>(src), bytes); return dest; } unsigned char* pos = dest; for (size_t i = 0; i < bytes / sizeof(Type); ++i) { Type tmp = reverse_copy(src[i]); std::memcpy(pos, &tmp, sizeof(Type)); pos += sizeof(Type); } return dest; } template <class Type> inline char* memcpy(const char* dest, const Type* src, int bytes) { return memcpy(reinterpret_cast<unsigned char*>(dest), src, bytes); } /* --------------------------------------------------------------------- */ // memcpy /* --------------------------------------------------------------------- */ template <class Type> inline Type* memcpy(Type* dest, const unsigned char* src, int bytes) { if (endian::is_little()) { std::memcpy(dest, reinterpret_cast<const unsigned char*>(src), bytes); return dest; } const unsigned char* pos = src; for (size_t i = 0; i < bytes / sizeof(Type); ++i) { std::reverse_copy(pos, pos + sizeof(Type), reinterpret_cast<unsigned char*>(&dest[i])); pos += sizeof(Type); } return dest; } template <class Type> inline Type* memcpy(Type* dest, const char* src, int bytes) { return memcpy(dest, reinterpret_cast<const unsigned char*>(src), bytes); } }
今後,多バイト長の配列をコピーする場合は std::memcpy() の代わりにこちらを使って行こうかなと思います.