アラインメントを気にしない読み出しを行う

仕事でARMアーキテクチャを使っているときにアラインメントをまたぐ読み出しを行ってプログラムが異常終了するケースがありました。
そのときは ARM gcc ¥Ð¥Ã¥É¥Î¥¦¥Ï¥¦½¸: ¥¢¥é¥¤¥ó¥á¥ó¥È に書いてあるようなバッドノウハウで切り抜けましたが、そもそもアラインメントを気にしない読み出しを行う関数があるといいのでは?と思い、読み出しを行うC++のテンプレート関数を書いてみました。
第一引数に読み出す領域の先頭ポインタを渡し、第二引数に読み出すメモリオーダリングを指定します。無駄にPDPエンディアンにも対応してます。f(^^;)
指定された領域からはテンプレート引数に渡した型のサイズ分を読み出し、指定されたメモリオーダリングで解釈された値を返します。

#include <stddef.h>
#include <stdint.h>
#include <endian.h>

static const int MEM_B_ORDER_BIG =     0;
static const int MEM_B_ORDER_LITTLE =  1;
static const int MEM_B_ORDER_MIDDLE =  2;

static const int MEM_B_ORDER_PDP =     MEM_B_ORDER_MIDDLE;
static const int MEM_B_ORDER_NETWORK = MEM_B_ORDER_BIG;
static const int MEM_B_ORDER_HOST =
 #if __BYTE_ORDER == __BIG_ENDIAN
  MEM_B_ORDER_BIG;
 #elif __BYTE_ORDER == __LITTLE_ENDIAN
  MEM_B_ORDER_LITTLE;
 #elif __BYTE_ORDER == __PDP_ENDIAN
  MEM_B_ORDER_PDP;
 #else
  MEM_B_ORDER_BIG;
  #warning "Host byte order is suspicious... Assumed big endian."
 #endif

template <typename INT_T>
INT_T Readout(const void* headPtr, int accessOrder)
{
	INT_T var = INT_T();
	const uint8_t* _ptr = reinterpret_cast<const uint8_t*>(headPtr);
	size_t varSize = sizeof(INT_T);

	switch (accessOrder)
	{
	case MEM_B_ORDER_BIG:
		for (size_t offset = 0; offset < varSize; ++offset)
		{
			var |= static_cast<INT_T>(_ptr[offset]) << ((varSize - offset - 1) * 8);
		}
		break;
	case MEM_B_ORDER_LITTLE:
		for (size_t offset = 0; offset < varSize; ++offset)
		{
			var |= static_cast<INT_T>(_ptr[offset]) << (offset * 8);
		}
		break;
	case MEM_B_ORDER_PDP:
		for (size_t offset = 0; offset < varSize; offset += 2)
		{
			var |= static_cast<INT_T>(_ptr[offset]) << ((varSize - offset - 2) * 8);
			var |= static_cast<INT_T>(_ptr[offset + 1]) << ((varSize - offset - 1) * 8);
		}
		break;
	}

	return var;
}

template <typename INT_T>
void Readout(INT_T* out, const void* headPtr, int accessOrder)
{
	*out = Readout<INT_T>(headPtr, accessOrder);
}

使用例は以下のような感じです。

#include <stdio.h>

int main()
{
	uint8_t Be[] = {0x0a, 0x0b, 0x0c, 0x0d};
	uint8_t Le[] = {0x0d, 0x0c, 0x0b, 0x0a};
	uint8_t Pe[] = {0x0b, 0x0a, 0x0d, 0x0c};

	printf("Parsed Be as Big Endian:    0x%08x\n", Readout<uint32_t>(Be, MEM_B_ORDER_BIG));
	printf("Parsed Le as Little Endian: 0x%08x\n", Readout<uint32_t>(Le, MEM_B_ORDER_LITTLE));
	printf("Parsed Pe as Middle Endian: 0x%08x\n", Readout<uint32_t>(Pe, MEM_B_ORDER_MIDDLE));

	uint8_t Be64[] = {0x0a, 0x0b, 0x0c, 0x0d, 0x1a, 0x1b, 0x1c, 0x1d};
	uint8_t Le64[] = {0x1d, 0x1c, 0x1b, 0x1a, 0x0d, 0x0c, 0x0b, 0x0a};
	uint8_t Pe64[] = {0x0b, 0x0a, 0x0d, 0x0c, 0x1b, 0x1a, 0x1d, 0x1c};

	printf("Parsed Be64 as Big Endian:    0x%016llx\n", Readout<uint64_t>(Be64, MEM_B_ORDER_BIG));
	printf("Parsed Le64 as Little Endian: 0x%016llx\n", Readout<uint64_t>(Le64, MEM_B_ORDER_LITTLE));
	printf("Parsed Pe64 as Middle Endian: 0x%016llx\n", Readout<uint64_t>(Pe64, MEM_B_ORDER_MIDDLE));

	printf("Parsed &Be64[1] as Big Endian:    0x%08x\n", Readout<uint32_t>(&Be64[1], MEM_B_ORDER_BIG));
	printf("Parsed &Le64[1] as Little Endian: 0x%08x\n", Readout<uint32_t>(&Le64[1], MEM_B_ORDER_LITTLE));
	printf("Parsed &Pe64[1] as Middle Endian: 0x%08x\n", Readout<uint32_t>(&Pe64[1], MEM_B_ORDER_MIDDLE));

	int i = -256, j= 1235, j_clone;
	printf("Parsed i as Host Byte Order: 0x%08x, %d\n", Readout<int>(&i, MEM_B_ORDER_HOST), Readout<int>(&i, MEM_B_ORDER_HOST));
	Readout(&j_clone, &j, MEM_B_ORDER_HOST);
	printf("Parsed j as Host Byte Order: 0x%08x, %d\n", j_clone, j_clone);

	return 0;
}

Readout関数は結果を返り値として受け取るパターンとポインタ渡しでセットしてもらうパターンと両方用意してみました。今のところ受け取る変数は汎整数型限定です。
本当は

  • デフォルトコンストラクタが定義されている。
  • operator|=()が定義されている。
  • copyableである。

を満たしていればクラスでも使用可能にしたかったのですが、

	size_t varSize = sizeof(INT_T);

の箇所で代入される変数とオブジェクトのサイズが等しい保証なんてないので不可能です。

template <typename INT_T, unsigned SIZE = sizeof(INT_T)>
INT_T Readout(const void* headPtr, int accessOrder)

のようにしようと思ったら、関数テンプレートはデフォルト引数が使えないとか…。C++0xでは関数テンプレートのデフォルト引数が使えるようになるらしいですが。クラスを定義してstaticメンバ関数にすると出来そうな予感がするので、また後日試してみます。