Posts Tagged Pointer

メモリのアラインメントを確認する

そもそもの話。
なぜ「ゲームプログラマになる前に覚えておきたい技術(以下「セガ本」)」を読んでる途中で「C言語ポインタ完全制覇(以下「ポインタ本」)」を読むことになったか?
それは、セガ本にデータ型のサイズで割りきれるアドレスでデータを読み込まないと、x86系(いわゆるIntel、AMD系)以外のCPUではクラッシュし、x86でもデータを2回読むことになってパフォーマンスが落ちる、と書いてあったから。
で、パッと見て理解できないでいたら、時を同じくしてポインタ本が届き、目次にアラインメントの記述が。
と言うわけで、ポインタ本を読み進めてみた次第。

そこで思ったのが、この本、本当に為になる。
俺みたいなCの見習いの肩もみの家の便所掃除みたいな人間にはいろいろ発見が多すぎて驚く。
例えば

#include <stdio.h>
int main()
{
int i;
char s[] = "abcdefghij";
for ( i = 0; i < sizeof( s ) - 1; ++i ) {
printf( "%d:%c\n", i, i[s] );
}
}

なんてコード。
実行するとこう。

McLaren% gcc -o pointer_test pointer_test.c
McLaren% ./pointer_test
0:a
1:b
2:c
3:d
4:e
5:f
6:g
7:h
8:i
9:j

9行目のi[s]はs[i]の間違いと思いがちだけど、”これでも”問題ない。
出来るか出来ないかの話であって、常識的にこの書き方をして良いかは別の話(そしてもちろんダメ)。
この表記はコンパイル時に

printf( "%d:%c\n", i, *( i + s ) );

と変換しているだけ。
確かにポインタ+数値でそのポインタの型の分だけ先へ進む機能がある。
とまぁ、なかなか興味深い。

さてさて本題。
ようやくアラインメントが書いてあるページまでたどり着いた。
どうやら現代のコンパイラはかなりその辺りも配慮しているらしく、次に置かれるデータがそのデータ型で割り切れない場合、”詰め物”をしてくれるんだとか。
実際コードにして試してみまふ。

// alignment_check.c
#include <stdio.h>
typedef struct {
char ch;
double dn;
short sn;
int nu;
} MyStruct;
int main()
{
MyStruct hoge;
unsigned long gap1, gap2, gap3;
printf( "sizeof char\t%lu\n", sizeof( char ) );
printf( "sizeof double\t%lu\n", sizeof( double ) );
printf( "sizeof short\t%lu\n", sizeof( short ) );
printf( "sizeof int\t%lu\n\n", sizeof( int ) );
printf( "sizeof MyStruct\t%lu\n\n", sizeof( MyStruct ) );
printf( "\t&hoge's hexadecimal memory address.\n" );
printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
printf( "\t&hoge\t\tMyStruct\t%p\t%4lu\n", &hoge, sizeof( hoge ) );
printf( "\t&hoge.ch\tchar\t\t%p\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
gap1 = (unsigned long)&hoge.dn -
( (unsigned long)&hoge.ch + sizeof( hoge.ch ) );
printf( "gap1:%lu\n", gap1 );
printf( "\t&hoge.dn\tdouble\t\t%p\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
gap2 = (unsigned long)&hoge.sn -
( (unsigned long)&hoge.dn + sizeof( hoge.dn ) );
printf( "gap2:%lu\n", gap2 );
printf( "\t&hoge.sn\tshort\t\t%p\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
gap3 = (unsigned long)&hoge.nu -
( (unsigned long)&hoge.sn + sizeof( hoge.sn ) );
printf( "gap3:%lu\n", gap3 );
printf( "\t&hoge.nu\tint\t\t%p\t%4lu\n\n", &hoge.nu, sizeof( hoge.nu ) );
printf( "char + gap1 + double + gap2 + short + gap3 + int = %lu\n\n",
sizeof( char ) + gap1 +
sizeof( double ) + gap2 +
sizeof( short ) + gap3 +
sizeof( int ) );
printf( "&hoge's decimal memory address (with alighnment gap size).\n" );
printf( "\tname\t\ttype\t\taddress\t\tsize\n" );
printf( "\t&hoge\t\tMyStruct\t%ll\t%4lu\n", &hoge, sizeof( hoge ) );
printf( "\t&hoge.ch\tchar\t\t%lu\t%4lu\n", &hoge.ch, sizeof( hoge.ch ) );
printf( "gap1:%lu\n", gap1 );
printf( "\t&hoge.dn\tdouble\t\t%lu\t%4lu\n", &hoge.dn, sizeof( hoge.dn ) );
printf( "gap2:%lu\n", gap2 );
printf( "\t&hoge.sn\tshort\t\t%lu\t%4lu\n", &hoge.sn, sizeof( hoge.sn ) );
printf( "gap3:%lu\n", gap3 );
printf( "\t&hoge.nu\tint\t\t%lu\t%4lu\n", &hoge.nu, sizeof( hoge.nu ) );
return 0;
}

まぁ、こんだけ親切に情報盛ったから、アドレス表記だけで良いとは思ったけど、一応アドレスを10進数にしたやつも記載。
(つっても、64ビット環境においてunsigned long程度でアドレス表示して間に合わない気がするけど、ギャップを目視することが目的だから、あえてこのまま。
あれ?ラップトップのMacでgcc動かしたときって64ビット扱い?
・・・ま、今回は気にしないことに。
どうせテストだし。。。
まともに64ビットアドレス書いてたら、京単位らしいから・・・ね。)

で、結果。

McLaren% gcc -o alignment_check alignment_check.c
alignment_check.c: In function ‘main’:
alignment_check.c:48: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘struct MyStruct *’
alignment_check.c:49: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘char *’
alignment_check.c:52: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘double *’
alignment_check.c:55: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘short int *’
alignment_check.c:58: warning: format ‘%lu’ expects type ‘long unsigned int’, but argument 2 has type ‘int *’
McLaren% ./alignment_check
sizeof char     1
sizeof double   8
sizeof short    2
sizeof int      4
sizeof MyStruct 24
&hoge's hexadecimal memory address.
name            type            address         size
&hoge           MyStruct        0x7fff5fbfef10    24
&hoge.ch        char            0x7fff5fbfef10     1
gap1:7
&hoge.dn        double          0x7fff5fbfef18     8
gap2:0
&hoge.sn        short           0x7fff5fbfef20     2
gap3:2
&hoge.nu        int             0x7fff5fbfef24     4
char + gap1 + double + gap2 + short + gap3 + int = 24
&hoge's decimal memory address (with alighnment gap size).
name            type            address         size
&hoge           MyStruct        140734799802128   24
&hoge.ch        char            140734799802128    1
gap1:7
&hoge.dn        double          140734799802136    8
gap2:0
&hoge.sn        short           140734799802144    2
gap3:2
&hoge.nu        int             140734799802148    4

こんな塩梅。
ワーニングは全部アドレスを無理矢理10進数で出したため。
「ポインタ本」と同じように構造体で確認した。
こうすれば普通の処理系では間に別の変数を入れたりする余地はあるまい:-)
で、確かにアドレスを次のメンバ変数のサイズで割り切れない場合、ギャップが出ている。
なるほど、こうすればどのCPUでもスマートに動くというわけですかい。
勉強になった。

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

著者/訳者:前橋 和弥

出版社:技術評論社( 2001-01-01 )

定価:

Amazon価格:¥ 2,462

単行本 ( 323 ページ )

ISBN-10 : 4774111422

ISBN-13 : 9784774111421



ゲームプログラマになる前に覚えておきたい技術

著者/訳者:平山 尚

出版社:秀和システム( 2008-11-14 )

定価:

Amazon価格:¥ 4,860

単行本 ( 872 ページ )

ISBN-10 : 4798021180

ISBN-13 : 9784798021188


Post to Twitter

, , , ,

No Comments

スタックオーバーフローをテスト

最近、「C言語ポインタ完全制覇 (標準プログラマーズライブラリ)」という本を読んでる。
タイトルの通り、C言語の、それもポインタだけに的を絞った本。
これがなかなかどうして面白くて、文字も大きいのですらすら進んでしまう。
ただ、簡単かと言われれば全く違って、興味深くて考えさせられるような話題ばかり。

ようやく100ページまで進んだ。
そしたらセキュリティーホールの温床になると言うスタックオーバーフローをテストしようという楽しいコードが書いてあった。
で、試したくなるじゃないですか。
若干情報多めに書いて写経してみたのが下記。

// stack_overflow_test.c
#include <stdio.h>
void hello()
{
static int i = 0;
fprintf( stderr, "stderr:\tHello xD\n" );
printf( "\tNo.%d Hello:P\n", i++ );
}
void func()
{
void        *buf[10];
static int  i;
printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );
for ( i = 0; i < 16; ++i ) {
buf[i] = hello;
printf( "buf[%d]\t%p\n", i, &buf[i] );
}
printf( "\n\tEnd of func()\n\n" );
}
int main()
{
int buf[1000];
printf( "Start\n\n" );
func();
printf( "End\n\n" );
return 0;
}

で、実行するとこう。

% gcc -o stack_overflow_test stack_overflow_test.c
% ./stack_overflow_test
Start
buf     0x7fff5fbfdf20
*buf    0x100000eee
&i      0x10000108c
&hello  0x100000d1c
buf[0]  0x7fff5fbfdf20
buf[1]  0x7fff5fbfdf28
buf[2]  0x7fff5fbfdf30
buf[3]  0x7fff5fbfdf38
buf[4]  0x7fff5fbfdf40
buf[5]  0x7fff5fbfdf48
buf[6]  0x7fff5fbfdf50
buf[7]  0x7fff5fbfdf58
buf[8]  0x7fff5fbfdf60
buf[9]  0x7fff5fbfdf68
buf[10] 0x7fff5fbfdf70
buf[11] 0x7fff5fbfdf78
buf[12] 0x7fff5fbfdf80
buf[13] 0x7fff5fbfdf88
buf[14] 0x7fff5fbfdf90
buf[15] 0x7fff5fbfdf98
End of func()
zsh: segmentation fault  ./stack_overflow_test

あれ~?
hello()が実行されてない・・・??
と言うわけで、落ちる前に一度hello()を呼んでみた。

#include <stdio.h>
void hello()
{
static int i = 0;
fprintf( stderr, "stderr:\tHello xD\n" );
printf( "\tNo.%d Hello:P\n", i++ );
}
void func()
{
void        *buf[10];
static int  i;
printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );
hello();
for ( i = 0; i < 16; ++i ) {
buf[i] = hello;
printf( "buf[%d]\t%p\n", i, &buf[i] );
}
printf( "\n\tEnd of func()\n\n" );
}
int main()
{
int buf[1000];
printf( "Start\n\n" );
func();
printf( "End\n\n" );
return 0;
}

するとこうなる。

% gcc -o stack_overflow_test stack_overflow_test.c
Start
buf     0x7fff5fbfdf20
*buf    0x100000eee
&i      0x10000108c
&hello  0x100000d10
stderr: Hello xD
No.0 Hello:P
buf[0]  0x7fff5fbfdf20
buf[1]  0x7fff5fbfdf28
buf[2]  0x7fff5fbfdf30
buf[3]  0x7fff5fbfdf38
buf[4]  0x7fff5fbfdf40
buf[5]  0x7fff5fbfdf48
buf[6]  0x7fff5fbfdf50
buf[7]  0x7fff5fbfdf58
buf[8]  0x7fff5fbfdf60
buf[9]  0x7fff5fbfdf68
buf[10] 0x7fff5fbfdf70
buf[11] 0x7fff5fbfdf78
buf[12] 0x7fff5fbfdf80
buf[13] 0x7fff5fbfdf88
buf[14] 0x7fff5fbfdf90
buf[15] 0x7fff5fbfdf98
End of func()
stderr: Hello xD
No.1 Hello:P
stderr: Hello xD
No.2 Hello:P
stderr: Hello xD
No.3 Hello:P
stderr: Hello xD
No.4 Hello:P
stderr: Hello xD
No.5 Hello:P
zsh: segmentation fault  ./stack_overflow_test

これでしっかり動いた。
5回勝手(不正)に実行されちょる。

本の説明によるとメモリの状態は下記のような感じとなるらしい。
–ここ移行に別の変数が追加されていく–
buf[0] 0x7fff5fbfdf20
buf[1] 0x7fff5fbfdf28
buf[2] 0x7fff5fbfdf30
buf[3] 0x7fff5fbfdf38
buf[4] 0x7fff5fbfdf40
buf[5] 0x7fff5fbfdf48
buf[6] 0x7fff5fbfdf50
buf[7] 0x7fff5fbfdf58
buf[8] 0x7fff5fbfdf60
buf[9] 0x7fff5fbfdf68
==他の自動変数==
==関数終了時に戻るアドレス情報など==
==その他の領域==

考察:buf[10]~buf[15]までの6個分余分に配列があると仮定して上がいてます、と(・・アレ?5回しか実行されてない??)。
自動変数を食い破り、呼び出し元の関数情報を破壊して行きます、と。
あ、わかった。。。
funcに自動変数iがあるから1回少ないんだ。
funcをこう変えた。

void func()
{
void        *buf[10];
static int  i;
int a, b, c, d, e; // 追加
printf( "buf\t%p\n*buf\t%p\n&i\t%p\n&hello\t%p\n\n", buf, *buf, &i, &hello );
hello();
for ( i = 0; i < 15; ++i ) {
buf[i] = hello;
printf( "buf[%d]\t%p\n", i, &buf[i] );
}
printf( "\n\tEnd of func()\n\n" );
}

結果はこう。
(ちなみに、ちょっとプログラムいじっちゃったからアドレスと表記が若干違う。。)

Start
&func   0x100000d3f
buf     0x7fff5fbfeea0
*buf    0xffffffffffffffff
&i      0x10000108c
&hello  0x100000cf8
stderr: Hello xD
No.0 Hello:P
buf[0]  0x7fff5fbfeea0
buf[1]  0x7fff5fbfeea8
buf[2]  0x7fff5fbfeeb0
buf[3]  0x7fff5fbfeeb8
buf[4]  0x7fff5fbfeec0
buf[5]  0x7fff5fbfeec8
buf[6]  0x7fff5fbfeed0
buf[7]  0x7fff5fbfeed8
buf[8]  0x7fff5fbfeee0
buf[9]  0x7fff5fbfeee8
buf[10] 0x7fff5fbfeef0
buf[11] 0x7fff5fbfeef8
buf[12] 0x7fff5fbfef00
buf[13] 0x7fff5fbfef08
buf[14] 0x7fff5fbfef10
End of func()
End
zsh: segmentation fault  ./stack_overflow_test

ほら、実行されない。
ただ不思議なことに、

int a;

だけにした場合と、

int a, b, c, d;

までにした場合、

Start
&func   0x100000d3f
buf     0x7fff5fbfeeb0
*buf    0x7365745f776f6c66
&i      0x10000108c
&hello  0x100000cf8
stderr: Hello xD
No.0 Hello:P
buf[0]  0x7fff5fbfeeb0
buf[1]  0x7fff5fbfeeb8
buf[2]  0x7fff5fbfeec0
buf[3]  0x7fff5fbfeec8
buf[4]  0x7fff5fbfeed0
buf[5]  0x7fff5fbfeed8
buf[6]  0x7fff5fbfeee0
buf[7]  0x7fff5fbfeee8
buf[8]  0x7fff5fbfeef0
buf[9]  0x7fff5fbfeef8
buf[10] 0x7fff5fbfef00
buf[11] 0x7fff5fbfef08
buf[12] 0x7fff5fbfef10
buf[13] 0x7fff5fbfef18
buf[14] 0x7fff5fbfef20
End of func()
stderr: Hello xD
No.1 Hello:P
stderr: Hello xD
No.2 Hello:P

となる。
エラーは起きない。
けど変。

結論:戻り先アドレスまで上がいたら、そこにある情報を実行しちゃいました、的な感じの認識。

まぁ、何にせよ、バッファが溢れないように注意しないといつか痛い目見ますよ、と。
精進します。

C言語ポインタ完全制覇 (標準プログラマーズライブラリ)

著者/訳者:前橋 和弥

出版社:技術評論社( 2001-01-01 )

定価:

Amazon価格:¥ 2,462

単行本 ( 323 ページ )

ISBN-10 : 4774111422

ISBN-13 : 9784774111421


Post to Twitter

, , , ,

No Comments