いけむランド

はてダからやってきました

エンディアンとかアラインとか

同じプログラムなのに CPU により結果が異なる簡単な例。

 なぜ動かないのか。同じLinuxでも,プラットフォームに大きな相違点があるからだ。簡単に言えば,採用するCPUによって,細かなアーキテクチャの違いがあり,それがソフトウエアの動作に影響を与えている。例えば,2バイトのデータをメモリーに格納するとき,上位バイトと下位バイトの実際の格納位置は CPUによって異なる。開発者は,このバイト・オーダの違いを知っておく必要がある。「バイト単位で置いたデータを,ワード(32ビット)命令でアクセスするようなトリッキーなプログラムは移植性が低い」(竹岡氏)というわけだ。

 また,データ・アライメントの違いも大きい。x86系CPUでは,32ビットのデータがどのようにメモリー上に並んでいても,ワード命令でアクセスできる。しかし,RISC系CPUでは,きりのいいメモリー番地からしかワード・アクセスできない。したがって,アライメントに依存しているプログラムは問題が発生する。

以下のようなやる気のないプログラムで試してみる。

#include <stdio.h>

typedef union {
  char c[8];
  int  i[2];
} word;

int main(int argc, char** argv)
{
  word w;

  w.c[0] = 0x00000011;
  w.c[1] = 0x00000022;
  w.c[2] = 0x00000033;
  w.c[3] = 0x00000044;
  w.c[4] = 0x00000055;
  w.c[5] = 0x00000066;
  w.c[6] = 0x00000077;
  w.c[7] = 0x00000088;

  printf("        w.i[0] = 0x%08x (address:0x%08x)\n", w.i[0], w.i);
  printf("*(int*)&w.c[1] = 0x%08x (address:0x%08x)\n", *(int*)&w.c[1], &w.c[1]);
}

x86

まずは x86 の場合。cygwin でやってみた。

% uname -a
CYGWIN_NT-5.1 tanpopo 1.5.25(0.156/4/2) 2007-12-14 19:21 i686 Cygwin
% ./union.exe 
        w.i[0] = 0x44332211 (address:0x0022cce0)
*(int*)&w.c[1] = 0x55443322 (address:0x0022cce1)
%

トルエンディアンであるため、下位の byte から順に格納されている。
また、1 byte ずれた番地からもワード単位で読み出せることがわかる。

SPARC

手元には他の CPU はなかったため、こういう時のためにアカウントの申請をしていた FreeShells.ch にログインして、試してみる。

% uname -sm
NetBSD sparc64
% ./union.exe
        w.i[0] = 0x11223344 (address:0xffffca98)
Bus error (core dumped)
% gdb ./union.exe
GNU gdb 5.3nb1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "sparc64--netbsdelf"...
(gdb) run
Starting program: /home/fuddy/tmp/union.exe 
        w.i[0] = 0x11223344 (address:0xffffcaa8)

Program received signal SIGBUS, Bus error.
main (argc=1, argv=0xffffffffffffcc08) at union.c:23
23        printf("*(int*)&w.c[1] = 0x%08x (address:0x%08x)\n", *(int*)&w.c[1], &w.c[1]);
(gdb) 

ビッグエンディアンであるため、上位の byte から順に格納されていることがわかる。
また、きりのいい番地からの読み出しでないため、SIGBUS が発生している。

What you can, even on sparc, is having 32 or even 64 bit quantities
aligned to 16 bit in packed structs (and perhaps arrays). But you
have to make sure you are not triggering undefined behaviour (which
means sigbus here) by giving away pointer to unaligned data, nor forget
that even an packages struct as a whole has an enforced alignment.
(so having an struct with something on an +2 offset and and pointer
to that struct which is not 0(mod 4) may mean the data is aliged but
the compiler think it is misaligned and you get a buserror).

Still, I was surprised by this because my experience had been that SPARC generated a SIGBUS in response to a misaligned access. The few times that I had ever tried to work around misalignment (rather than actually fixing it), I had to resort to either specifying the -misalign compiler option or issuing a ST_FIX_ALIGN trap to turn on the kernel trap handler.