teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

5

スタック領域での割り当てられ方について補足

2019/10/19 03:00

投稿

raccy
raccy

スコア21770

answer CHANGED
@@ -9,8 +9,11 @@
9
9
  - 変数は環境および型によってサイズが決まっていて、`sizeof`演算子で取得できる。x86およびx86_64環境では、`int`は4、`double`は8である。各変数ではこのサイズが十分入る分の領域が確保される。
10
10
  - 変数は環境および型によって先頭のメモリが取れる位置が決まっており、`char`等1バイトの型を除けば、飛び飛びになる。これをアライメントといい、`_Alignof`演算子(stddef.hをincludeしていれば`alignof`でも可能)で取得できる(`_Alignof`はC11から)。x86およびx86_64環境では、`int`は4、`double`は8である。
11
11
  - スタックへ変数は連続して詰まれるが、アライメントによりその場所に置けない場合は、置ける場所までずれる。
12
- ※ 質問のコードからちょっと変更を加えた物ですが、 [https://godbolt.org/z/Ch2Uzw](https://godbolt.org/z/Ch2Uzw) と [https://godbolt.org/z/hKTXFR](https://godbolt.org/z/hKTXFR) を見比べればわかると思います。`int`はrbpとの差が4の倍数であれば置けますが、`double`は8の倍数の所にしか置けないため、二つ目のコードの`x`について[rpb-12]ではなく[rbp-16]になっています。なお、どちらも、変数毎にスタックへpushするのでは無く、最初に32バイトのスタック領域が確保するようにしています(**x86_64だからこうなのかは不明**)。**なぜ一つ目のコードが24バイトでは無く32バイトなのかはわかりません**。x86_64でのスタック領域確保は16バイト単位とか縛りがあるんでしょうか?詳しい人教えてください。
12
+ ※ 質問のコードからちょっと変更を加えた物ですが、 [https://godbolt.org/z/Ch2Uzw](https://godbolt.org/z/Ch2Uzw) と [https://godbolt.org/z/hKTXFR](https://godbolt.org/z/hKTXFR) を見比べればわかると思います。`int`はrbpとの差が4の倍数であれば置けますが、`double`は8の倍数の所にしか置けないため、二つ目のコードの`x`について[rpb-12]ではなく[rbp-16]になっています。
13
+ - 実際の所、x86_64のほとんどのコンパイラでは、ローカル変数毎にpushするのではなく、関数開始時にローカル変数が十分入る大きさのスタック領域を確保し、その領域に対してローカル変数を割り当てると言うことをしている。ただし、上から割り当てるのか、下から割り当てるのかはコンパイラによって異なる。GCCは上から下に向かって割り当てていく(pushで実装していた時代の名残と思われますが、ここら辺の歴史に詳しい人がいたら教えてください)。
14
+ ※ 前の項でどちらも、変数毎にスタックへpushするのでは無く、最初に32バイトのスタック領域が確保するようにしているのがわかると思います。ただ、**なぜ一つ目のコードが24バイトでは無く32バイトなのかはわかりません**。x86_64でのスタック領域確保は16バイト単位とか縛りがあるんでしょうか?詳しい人教えてください。
13
15
 
16
+
14
17
  上記のことからx86_64で、GCCで最適化オプション無しの場合(ただし一部のOSでは異なる場合がある)、上のアセンブリの結果も参考にすると、
15
18
 
16
19
  1. `a`はサイズ4なのでベースポインタ(関数がベースとするスタックの位置)より4少ない位置に配置される。この位置は4の倍数なのでアライメントも満たす。

4

条件がどんどん増える

2019/10/19 03:00

投稿

raccy
raccy

スコア21770

answer CHANGED
@@ -11,7 +11,7 @@
11
11
  - スタックへ変数は連続して詰まれるが、アライメントによりその場所に置けない場合は、置ける場所までずれる。
12
12
  ※ 質問のコードからちょっと変更を加えた物ですが、 [https://godbolt.org/z/Ch2Uzw](https://godbolt.org/z/Ch2Uzw) と [https://godbolt.org/z/hKTXFR](https://godbolt.org/z/hKTXFR) を見比べればわかると思います。`int`はrbpとの差が4の倍数であれば置けますが、`double`は8の倍数の所にしか置けないため、二つ目のコードの`x`について[rpb-12]ではなく[rbp-16]になっています。なお、どちらも、変数毎にスタックへpushするのでは無く、最初に32バイトのスタック領域が確保するようにしています(**x86_64だからこうなのかは不明**)。**なぜ一つ目のコードが24バイトでは無く32バイトなのかはわかりません**。x86_64でのスタック領域確保は16バイト単位とか縛りがあるんでしょうか?詳しい人教えてください。
13
13
 
14
- 上記のことからx86_64で、GCCで最適化オプション無しの場合、上のアセンブリの結果も参考にすると、
14
+ 上記のことからx86_64で、GCCで最適化オプション無しの場合(ただし一部のOSでは異なる場合がある)、上のアセンブリの結果も参考にすると、
15
15
 
16
16
  1. `a`はサイズ4なのでベースポインタ(関数がベースとするスタックの位置)より4少ない位置に配置される。この位置は4の倍数なのでアライメントも満たす。
17
17
  2. `b`はサイズ4なので`a`より4少ない、ベースポインタより8少ない位置に配置される。この位置は4の倍数なのでアライメントも満たす。
@@ -19,7 +19,7 @@
19
19
  4. `y`はサイズ8なので`x`より8少ない、ベースポインタより24少ない位置に配置される。この位置は8の倍数なのでアライメントも満たす。
20
20
  5. 関数自体は上の処理を見越して、あらかじめ32(なぜ24ではないかは私にはわかりません)少ない位置にスタックポインタを進めている。
21
21
 
22
- となります。異なるアーキテクチャ、コンパイラオプション、定義の順番が異なるコードでは違う動作になります。例えば、[clangに変更しただけ](https://godbolt.org/z/1uySIq)でも4,12,8という差になります。clangだと最初に4バイト確保しているようで、その分ズレて、`x`の時にアライメントにあわせてズレてしまったため、`b`と`x`の差がGCCと異なったと言うことです。この最初の4バイトは何かというと・・・すいません、調べてもわかりませんでした。
22
+ となります。異なるアーキテクチャ、OS、コンパイラ、コンパイラオプション、定義の順番が異なるコードでは違う動作になります。例えば、[clangに変更しただけ](https://godbolt.org/z/1uySIq)でも4,12,8という差になります。clangだと最初に4バイト確保しているようで、その分ズレて、`x`の時にアライメントにあわせてズレてしまったため、`b`と`x`の差がGCCと異なったと言うことです。この最初の4バイトは何かというと・・・すいません、調べてもわかりませんでした。
23
23
 
24
24
  ---
25
25
 

3

コンパイラだけの話ではないので

2019/10/18 12:11

投稿

raccy
raccy

スコア21770

answer CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  - ローカル(自動)変数がメモリ上に領域を確保する場合は、スタック領域またはスタック用としてコンパイラが指定した領域にスタックとして詰まれる。
6
6
  - x86やx86_64等の主に使われているCISCのCPUでは、スタック領域は仮想メモリ空間の後ろに配置され、前の方向に向かって詰まれていく(詰まれた分だけ番地は減る)。つまり、スタックにpushすればスタックの先頭を指し示すレジスタ上のスタックポインタは前に進み(減る)、popすればスタックポインタは後ろに進む(増える)。(RISCのCPUではそもそもスタック領域やスタックポインタが用意されていなかったり、スタックが終わりの方向に詰まれる(詰まれた分だけ番地は増える)というのもあるらしい)
7
- - 最適化を有効にしない(最適化無し、GCCであれば`-O0`をつけるようなもの)場合は、ほとんどのコンパイラでは、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないという場合もあるようです。コメントを参考にしてください。)
7
+ - 最適化を有効にしない(最適化無し、GCCであれば`-O0`をつけるようなもの)場合は、ほとんどのコンパイラや環境では、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないという場合もあるようです。コメントを参考にしてください。)
8
8
  - 変数に対して、アドレス演算子など変数のアドレスそのものに対する処理が含まれる場合は、最適化が有効でも必ずメモリ領域上に確保される。(n1570のどこかに書いてあったはず。逆に言うとそうでは無い変数はレジスタ上に現れるだけだったりするということ)
9
9
  - 変数は環境および型によってサイズが決まっていて、`sizeof`演算子で取得できる。x86およびx86_64環境では、`int`は4、`double`は8である。各変数ではこのサイズが十分入る分の領域が確保される。
10
10
  - 変数は環境および型によって先頭のメモリが取れる位置が決まっており、`char`等1バイトの型を除けば、飛び飛びになる。これをアライメントといい、`_Alignof`演算子(stddef.hをincludeしていれば`alignof`でも可能)で取得できる(`_Alignof`はC11から)。x86およびx86_64環境では、`int`は4、`double`は8である。

2

スタックに順番に詰まれない場合がある。

2019/10/18 12:07

投稿

raccy
raccy

スコア21770

answer CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  - ローカル(自動)変数がメモリ上に領域を確保する場合は、スタック領域またはスタック用としてコンパイラが指定した領域にスタックとして詰まれる。
6
6
  - x86やx86_64等の主に使われているCISCのCPUでは、スタック領域は仮想メモリ空間の後ろに配置され、前の方向に向かって詰まれていく(詰まれた分だけ番地は減る)。つまり、スタックにpushすればスタックの先頭を指し示すレジスタ上のスタックポインタは前に進み(減る)、popすればスタックポインタは後ろに進む(増える)。(RISCのCPUではそもそもスタック領域やスタックポインタが用意されていなかったり、スタックが終わりの方向に詰まれる(詰まれた分だけ番地は増える)というのもあるらしい)
7
- - 最適化を有効にしない(最適化無し、GCCであれば`-O0`をつけるようなもの)場合は、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないというコンパイラがあれば教えてください。)
7
+ - 最適化を有効にしない(最適化無し、GCCであれば`-O0`をつけるようなもの)場合は、ほとんどのコンパイラでは、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないという場合もあるようです。トを参考にしてください。)
8
8
  - 変数に対して、アドレス演算子など変数のアドレスそのものに対する処理が含まれる場合は、最適化が有効でも必ずメモリ領域上に確保される。(n1570のどこかに書いてあったはず。逆に言うとそうでは無い変数はレジスタ上に現れるだけだったりするということ)
9
9
  - 変数は環境および型によってサイズが決まっていて、`sizeof`演算子で取得できる。x86およびx86_64環境では、`int`は4、`double`は8である。各変数ではこのサイズが十分入る分の領域が確保される。
10
10
  - 変数は環境および型によって先頭のメモリが取れる位置が決まっており、`char`等1バイトの型を除けば、飛び飛びになる。これをアライメントといい、`_Alignof`演算子(stddef.hをincludeしていれば`alignof`でも可能)で取得できる(`_Alignof`はC11から)。x86およびx86_64環境では、`int`は4、`double`は8である。

1

スタック領域が存在しないアーキテクチャもあるので、ちょっと表現を変えた。

2019/10/18 11:56

投稿

raccy
raccy

スコア21770

answer CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  いや、減ってるじゃん!ってツッコミを誰もしないので私がしておきますが、細かく説明するとそう簡単なことではありません。かいつまんで述べると次のような事を知っておく必要があります。
4
4
 
5
- - ローカル(自動)変数がメモリ上に領域を確保する場合は、スタック領域に詰まれる。
6
- - x86やx86_64等の主に使われているCISCのCPUでは、スタックは仮想メモリ空間の後ろから前の方向に向かって詰まれていく(詰まれた分だけ番地は減る)。つまり、スタックにpushすればスタックの先頭を指し示すレジスタ上のスタックポインタは前に進み(減る)、popすればスタックポインタは後ろに進む(増える)。(RISCのCPUではそもそもスタックポインタが用意されていなかったり、スタックが終わりの方向に詰まれる(詰まれた分だけ番地は増える)というのもあるらしい)
5
+ - ローカル(自動)変数がメモリ上に領域を確保する場合は、スタック領域またはスタック用としてコンパイラが指定した領域スタックとして詰まれる。
6
+ - x86やx86_64等の主に使われているCISCのCPUでは、スタック領域は仮想メモリ空間の後ろに配置され、前の方向に向かって詰まれていく(詰まれた分だけ番地は減る)。つまり、スタックにpushすればスタックの先頭を指し示すレジスタ上のスタックポインタは前に進み(減る)、popすればスタックポインタは後ろに進む(増える)。(RISCのCPUではそもそもスタック領域やスタックポインタが用意されていなかったり、スタックが終わりの方向に詰まれる(詰まれた分だけ番地は増える)というのもあるらしい)
7
7
  - 最適化を有効にしない(最適化無し、GCCであれば`-O0`をつけるようなもの)場合は、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないというコンパイラがあれば教えてください。)
8
8
  - 変数に対して、アドレス演算子など変数のアドレスそのものに対する処理が含まれる場合は、最適化が有効でも必ずメモリ領域上に確保される。(n1570のどこかに書いてあったはず。逆に言うとそうでは無い変数はレジスタ上に現れるだけだったりするということ)
9
9
  - 変数は環境および型によってサイズが決まっていて、`sizeof`演算子で取得できる。x86およびx86_64環境では、`int`は4、`double`は8である。各変数ではこのサイズが十分入る分の領域が確保される。