C言語で作ったプログラムに於けるスタック領域の仕組みについて質問があります。
まず自分はスタック領域について以下のように理解しています(間違っているかもしれませんが)。
「スタック」とは、後入れ先出しのデータ構造である。
そしてそれを応用したメモリ領域が「スタック領域」である。
スタック領域には変数が宣言された順に積まれていき、変数がいらなくなったタイミングで上から順に削除されていく。
これにより、C言語の「スコープを抜けたら変数は破棄される」という仕様を実現でき、かつメモリの節約にもなる。
この認識が正しいかどうかを確かめるため、以下のようなコードを書いてみました。
C
1#include<stdio.h> 2int func() 3{ 4 int d,e,f; 5 printf("%p,%p,%p\n",&d,&e,&f); 6} 7int main() 8{ 9 int a,b,c; 10 printf("%p,%p,%p\n",&a,&b,&c); 11 func(); 12}
この実行結果が以下の通りです。
0x7ffff9c51f2c,0x7ffff9c51f28,0x7ffff9c51f24
0x7ffff9c51f0c,0x7ffff9c51f08,0x7ffff9c51f04
予想通り変数a,b,cは連続した領域に並んでおり、またd,e,fも連続しています。しかしcとdの間に謎のギャップが存在します。そこで以下のようなコードを書いてみました。
C
1#include<stdio.h> 2int *p1,*p2; 3void func() 4{ 5 int d,e,f;p2=&d; 6 printf("%p,%p,%p\n",&d,&e,&f); 7} 8int main() 9{ 10 int a,b,c;p1=&c; 11 printf("%p,%p,%p\n",&a,&b,&c); 12 func(); 13 printf("%d\n",sizeof(int)*(p1-p2)); 14}
このプログラムを何回か実行した結果が以下の通りです。
0x7fffd5b3aa4c,0x7fffd5b3aa48,0x7fffd5b3aa44
0x7fffd5b3aa2c,0x7fffd5b3aa28,0x7fffd5b3aa24
24
0x7fffdfa7338c,0x7fffdfa73388,0x7fffdfa73384
0x7fffdfa7336c,0x7fffdfa73368,0x7fffdfa73364
24
0x7fffc350ecec,0x7fffc350ece8,0x7fffc350ece4
0x7fffc350eccc,0x7fffc350ecc8,0x7fffc350ecc4
24
...
int型は(私の環境だと)4バイトの大きさを持つので、スタック上で変数cの領域が終わってから変数dの領域が始まるまでに20バイトの空白?があるようです。これはどういった意味を持つのでしょうか?
環境はWindows10 home(64bit)のWSL(ubuntu)、コンパイラはgcc8.3.0でオプションは特につけてないです。
追記
(あっているかは置いといて)結論が出たので勉強の意味も込めて書いておきます。
Zuishin様やcateye様がアドバイスしてくださったように一つ目のコードのアセンブルソースを確認(gcc -S -O0 -fno-asynchronous-unwind-tables <ソース名>)したところ、このようになっていました。
Assembly
1 .file "1.c" 2 .text 3 .section .rodata 4.LC0: 5 .string "%p,%p,%p\n" 6 .text 7 .globl func 8 .type func, @function 9func: 10 pushq %rbp 11 movq %rsp, %rbp 12 subq $16, %rsp 13 leaq -12(%rbp), %rcx 14 leaq -8(%rbp), %rdx 15 leaq -4(%rbp), %rax 16 movq %rax, %rsi 17 movl $.LC0, %edi 18 movl $0, %eax 19 call printf 20 nop 21 leave 22 ret 23 .size func, .-func 24 .globl main 25 .type main, @function 26main: 27 pushq %rbp 28 movq %rsp, %rbp 29 subq $16, %rsp 30 leaq -12(%rbp), %rcx 31 leaq -8(%rbp), %rdx 32 leaq -4(%rbp), %rax 33 movq %rax, %rsi 34 movl $.LC0, %edi 35 movl $0, %eax 36 call printf 37 movl $0, %eax 38 call func 39 movl $0, %eax 40 leave 41 ret 42 .size main, .-main 43 .ident "GCC: (GNU) 8.3.0" 44 .section .note.GNU-stack,"",@progbits
また、Chironian様のご指摘のようにやはりgccはスタックフレームの開始アドレスを16バイト単位に調整しているようです。上のアセンブリソースにsubq $16, %rsp
という命令があることからもそれが窺えますし、main関数内で宣言する変数をもっと増やしてみたらsubq $32, %rsp
と変化したことから間違いないかと思います。
最終的に、スタックは恐らくこんな感じになっているのではないかと考えました。(なぜか1列のテーブルが作れなかったのでしかたなく2列にしています)
パディング(16-12=4バイト) | |
変数f(4バイト) | |
変数e(4バイト) | |
変数d(4バイト) | |
func関数のベースポインタ(8バイト) | |
main関数への戻り番地(たぶん8バイト?) | |
パディング(16-12=4バイト) | |
変数c(4バイト) | |
変数b(4バイト) | |
変数a(4バイト) | |
main関数のベースポインタ(8バイト) |
これが変数cと変数dの間の20バイトのギャップの正体ではないかなあと思いました。
回答3件
あなたの回答
tips
プレビュー
下記のような回答は推奨されていません。
このような回答には修正を依頼しましょう。
2019/04/24 08:57
2019/04/24 09:21
2019/04/24 09:37