エンディアンを考慮したメモリコピー

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 エンディアンなどは想定していません.

追記

リンク先の記事を読み間違えていたようです.コメントでも指摘がありましたが,

  • Linuxgcc の場合,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() の代わりにこちらを使って行こうかなと思います.