対象環境を限定できる場合、バイト配列にリトルエンディアンで32bit値を書き込むときに、私は以下のように書いていました。
char buf[32]; *(int *)&buf[6] = 0x12345678;
非常に分かりにくいとご指摘を受けて色々と変形しましたが、どれもしっくり来ません。
ポインタ演算
*(int *)(buf + 6) = 0x12345678;
記述を分割
int *p = (int *)&buf[6]; *p = 0x12345678;
アドレスをポインタで表現しているため、キャストに惑わされてメモリ操作のイメージが湧きにくいのではないかと思いました。そこで敢えて即値だけを使い、理屈抜きで基本構文として覚えてもらおうという趣旨のスライドを作ってみました。
アセンブリ言語
まずアセンブリ言語でメモリに書き込むコードを提示します。
MOV BYTE PTR[0x00000001], 0x12 MOV WORD PTR[0x00000004], 0xFECA MOV DWORD PTR[0x0000000A], 0xEFBEADDE
要点は「サイズを指定してメモリに書き込む」ということです。もしサイズを指定せずに書いた場合、何バイト書き換えるのか分かりません。
× MOV [0x00000001], 0x12
そのため1バイトを指定しているのがBYTE PTRです。gasでは接尾辞を付ける書き方もあります。
MOVB [0x00000001], 0x12
即値指定
次にアセンブリ言語をC言語に直訳して、即値でアドレスを指定して書き込むコードを提示します。
*(char *)0x00000001 = 0x12; *(short *)0x00000004 = 0xFECA; *(long *)0x0000000A = 0xEFBEADDE;
このようなコードはあまり見かけないため、説明用の擬似コードという印象を与えて、逆に混乱を招いたようです。でもこれは実際に使える構文で擬似コードではありません。
使用例
アドレスの即値指定を実際に使ってみます。
【注】Windows Vista以降ではASLR(wikipedia:アドレス空間配置のランダム化)が推奨されていますが、今回は説明の便宜上、固定アドレスを使用します。
次のようなプログラムで配列のアドレスを調べます。
addr1.c
#include <stdio.h> unsigned char buf[16]; int main(void) { int i; printf("%p:", buf); for (i = 0; i < sizeof(buf); i++) printf(" %02x", buf[i]); printf("\n"); return 0; }
実行結果
$ gcc addr1.c $ ./a.exe 00405068: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
アドレスを即値指定してメモリを書き換えてみます。
addr2.c
#include <stdio.h> unsigned char buf[16]; int main(void) { int i; *(char *)0x00405069 = 0x12; *(short *)0x0040506c = 0xfeca; *(long *)0x00405072 = 0xefbeadde; printf("%p:", buf); for (i = 0; i < sizeof(buf); i++) printf(" %02x", buf[i]); printf("\n"); return 0; }