IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

テンプレートを使って数値をリトルエンディアン形式のバイト列に変換する

boost/spirit/home/support/detail/integer/endian.hpp を参考に書いてみた

以下のような感じで定義しておいて

template <class T, std::size_t S>
struct little_endian {

    static inline void set(char* const buf, const T &t) {
        *buf = t & 0xff;
        little_endian<T, S - 1>::set(buf + 1, t >> 8); 
    }   

    static inline T get(const char* const buf) {
        return *buf | little_endian<T, S - 1>::get(buf + 1) << 8;
    }   
}; 

template <class T>
struct little_endian<T, 0> {

    static inline void set(char* const buf, const T &t) { } 

    static inline T get(const char* const buf) { return 0; }
}; 

以下のように使う

char buf[sizeof(int)];

// バイト列化
little_endian<int, sizeof(int)>::set(buf, 1000);

// 復元
int i = little_endian<int, sizeof(int)>::get(buf);

アセンブラを見てみる

たとえば、以下のようにインスタンス化してみると

#include <cstddef>

template <class T, std::size_t S>
struct little_endian {

    static inline void set(char* const buf, const T &t) {
        *buf = t & 0xff;
        little_endian<T, S - 1>::set(buf + 1, t >> 8);
    }

    static inline T get(const char* const buf) {
        return *buf | little_endian<T, S - 1>::get(buf + 1) << 8;
    }
};

template <class T>
struct little_endian<T, 0> {

    static inline void set(char* const buf, const T &t) { }

    static inline T get(const char* const buf) { return 0; }
};

// 明示的にインスタンス化する
template void little_endian<int, sizeof(int)>::set(char* const buf, const int &t);
template int little_endian<int, sizeof(int)>::get(const char* const buf);

以下のようなアセンブラになる

_ZN13little_endianIiLm4EE3setEPcRKi:
    movl    (%rsi), %eax
    movb    %al, (%rdi)
    movl    (%rsi), %eax
    sarl    $8, %eax
    movb    %al, 1(%rdi)
    sarl    $8, %eax
    movb    %al, 2(%rdi)
    sarl    $8, %eax
    movb    %al, 3(%rdi)
    ret

_ZN13little_endianIiLm4EE3getEPKc:
    movsbl  2(%rdi),%edx
    movsbl  3(%rdi),%eax
    sall    $8, %eax
    orl %edx, %eax
    movsbl  1(%rdi),%edx
    sall    $8, %eax
    orl %edx, %eax
    movsbl  (%rdi),%edx
    sall    $8, %eax
    orl %edx, %eax
    ret

テンプレートを使わない場合のアセンブラを見てみる

試しに、以下のように for 文を使うと

void little_endian_set(char* const buf, int a)
{
    for (std::size_t i = 0; i < sizeof(int); ++i)
    {
        buf[i] =a & 0xff;
        a >>= 8;
    }
}

int little_endian_get(const char* const buf)
{
    int r = 0;
    for (std::size_t i = 0; i < sizeof(int); ++i)
    {
        r = buf[i] | r << 8;
    }
    return r;
}

以下のようなアセンブラになった

_Z17little_endian_setPci:
    movb    %sil, (%rdi)
    movl    %esi, %eax
    sarl    $8, %eax
    movb    %al, 1(%rdi)
    movl    %esi, %eax
    sarl    $16, %eax
    movb    %al, 2(%rdi)
    sarl    $24, %esi
    movb    %sil, 3(%rdi)
    ret

_Z17little_endian_getPKc:
    movsbl  (%rdi),%eax
    sall    $8, %eax
    movsbl  1(%rdi),%edx
    orl %edx, %eax
    sall    $8, %eax
    movsbl  2(%rdi),%edx
    orl %edx, %eax
    sall    $8, %eax
    movsbl  3(%rdi),%edx
    orl %edx, %eax
    ret

あれ、あんまり大差ない?