aの番地は 0xffffcbfc
bの番地は 0xffffcbf8
xの番地は 0xffffcbf0
yの番地は 0xffffcbe8
番地がどのように増えていっているかを考察しないといけないのですが、
なぜこのように増えていくのかがわかりません。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/16 05:55 編集
2019/10/16 07:41
2019/10/16 08:12 編集
2019/10/16 09:18
2019/10/17 03:01
2019/10/17 03:13
回答9件
0
アセンブリコードに直してから研究してください。でなければどの変数がどこにどのように割り当てられるかは不定です。レジスタに割り当てられることもありますし、もしかしたらパッキングされることもあるでしょう。
C言語のコードをアセンブラ出力を確認しながら最適化する | 組込みエンジニアの思うところ
これは gcc ですが、コンパイラによってやり方は違います。また出力されるコードもコンパイラや最適化オプションによってそれぞれですから、同じコードなのに別のアドレスに割り当てられることもあります。
追記
C
1#include <stdio.h> 2#include <stdlib.h> 3 4int main(int argc, char* argv[]) { 5 int a = 0; 6 int b = 0; 7 char c = 0; 8 char d = 0; 9 int e = 0; 10 printf("a:%X, b:%X, c:%X, d:%X, e:%X\n", (unsigned)&a, (unsigned)&b, (unsigned)&c, (unsigned)&d, (unsigned)&e); 11 return 0; 12}
Visual Studio の cl で上記ソースをコンパイルし、実行した結果です。
a:6757FD6C, b:6757FD68, c:6757FD61, d:6757FD60, e:6757FD64
御覧の通り、一番最後に宣言された e が b と c の間にあります。c と d は 1 バイトなので、その直後(スタックはアドレス番号が小さいほど後になります)に int 型を割り当てると速度的に不利になるため、アライメントを合わせるために前に置かれているのです。このアライメントというのは、たとえば 16 ビットのコンピューターなら 16 ビット毎、64 ビットなら 64 ビット毎にある境界のことで、データをこの境界に合わせて置かないと読み書きが遅くなります。
ですから、char 型が 4 つあった場合、対象コンピューターが 16 ビットなのか 64 ビットなのかによって配置も変わる可能性があります。
そして、今回は後に置かれていますが、コンパイラによっては後に置かずアドレス調整のみで対応する場合もあるかもしれません。この場合、扱うデータによってはスタックの使用量で不利になります。
また、これはローカル変数なのでスタックに割り当てられていると推測されますが、必ずそうなるとは限りません。レジスタに割り当てられた場合、& でアドレスを取得できないので、アセンブリコード必須になります。
ソースレベルで判断できるなど笑止千万です。そんなものは対象の環境によってもコンパイラによっても最適化によっても変わってくる話です。推測は可能ですが、それはあくまでも推測にすぎません。
また今回はアドレスを出力させていますが、本番のコードでは必ずしも出力させるとは限りません。出力させるコードとさせないコードではコンパイル結果も変わるので、ソースコードをいじることでいじらなかったコードの状態を判断することはできません。
追記
アセンブリコードが無いので、「その結果から推測されること」しか考察できません。スタックが使われていることも、そのスタックが後ろからプッシュされていることも推測です。
double が 8 バイトで int が 4 バイトのメモリを使用してしるようです。
スタックに順に保存されていると推測するのが妥当でしょう。
変数 | 開始 | 終了 | バイト数 |
---|---|---|---|
a | ffffcbfc | ffffcbff | 4 |
b | ffffcbf8 | ffffcbfb | 4 |
x | ffffcbf0 | ffffcbf7 | 8 |
y | ffffcbe8 | ffffcbef | 8 |
このように割り当てられていると思います。まずはスタックポインタが ffffcc00 を指しているところに 4 バイトの a がプッシュされたので、スタックポインタは 4 減って ffffcbfc を指し、そこからの 4 バイトに a の内容が書き込まれます。次に 4 バイトの b がプッシュされたのでスタックポインタは 4 減って ffffcbf8 を指し、そこからの 4 バイトに b の内容が書き込まれます。
このように宣言された順にプッシュされているようです。
しかし、既に書いたようにこれは「たまたま」です。結果を見た上で「最適化の影響はないんだな、ローカル変数にはスタックを使ってるんだな」という想像はつきますが、常にこうなるわけではありません。
ターゲットが 64 ビットであればまた順序が変わったかもしれません。
そもそもスタックに一つ一つプッシュされたのではなく、計算でアドレスを決められた可能性の方が高いかもしれません。
考察せよと改めて言われた場合、全てを推測で片付けたのではレポートとしてお粗末なものになるのではないでしょうか。
投稿2019/10/16 03:31
編集2019/10/17 04:44総合スコア28656
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/16 03:44
2019/10/16 04:04 編集
2019/10/16 08:32
2019/10/16 08:39
2019/10/16 08:41
2019/10/16 08:50
2019/10/16 08:52
2019/10/16 09:01
2019/10/16 09:02
2019/10/16 09:04
2019/10/16 09:08
2019/10/16 11:47
2019/10/16 13:28
2019/10/17 01:24
2019/10/17 01:30
2019/10/17 04:45
2019/10/17 04:51 編集
2019/10/17 13:18
2019/10/17 13:21
2019/10/17 13:23
2019/10/17 13:35
2019/10/17 13:37
2019/10/17 14:05
2019/10/17 14:09
2019/10/17 14:14
2019/10/17 14:21
2019/10/17 14:37
2019/10/17 14:41
2019/10/17 23:47
2019/10/17 23:55
2019/10/18 00:02
2019/10/18 00:26
2019/10/18 00:53
2019/10/18 00:54
2019/10/18 00:59
2019/10/18 01:03
2019/10/18 01:33
2019/10/18 01:40
2019/10/18 01:49
2019/10/18 02:29 編集
2019/10/18 02:31
2019/10/18 02:34
2019/10/18 03:01 編集
2019/10/18 02:59
2019/10/18 04:07
2019/10/18 04:39
2019/10/18 04:50
2019/10/18 05:51 編集
2019/10/19 18:48
2019/10/19 22:36 編集
0
ベストアンサー
なぜこのように増えていくのかがわかりません。
いや、減ってるじゃん!ってツッコミを誰もしないので私がしておきますが、細かく説明するとそう簡単なことではありません。かいつまんで述べると次のような事を知っておく必要があります。
- ローカル(自動)変数がメモリ上に領域を確保する場合は、スタック領域またはスタック用としてコンパイラが指定した領域にスタックとして詰まれる。
- x86やx86_64等の主に使われているCISCのCPUでは、スタック領域は仮想メモリ空間の後ろに配置され、前の方向に向かって詰まれていく(詰まれた分だけ番地は減る)。つまり、スタックにpushすればスタックの先頭を指し示すレジスタ上のスタックポインタは前に進み(減る)、popすればスタックポインタは後ろに進む(増える)。(RISCのCPUではそもそもスタック領域やスタックポインタが用意されていなかったり、スタックが終わりの方向に詰まれる(詰まれた分だけ番地は増える)というのもあるらしい)
- 最適化を有効にしない(最適化無し、GCCであれば
-O0
をつけるようなもの)場合は、ほとんどのコンパイラや環境では、ローカル変数は定義した順番でスタックに詰まれていく。(最適化無効でもスタックに詰まれる順番が定義した順になるとは限らないという場合もあるようです。コメントを参考にしてください。) - 変数に対して、アドレス演算子など変数のアドレスそのものに対する処理が含まれる場合は、最適化が有効でも必ずメモリ領域上に確保される。(n1570のどこかに書いてあったはず。逆に言うとそうでは無い変数はレジスタ上に現れるだけだったりするということ)
- 変数は環境および型によってサイズが決まっていて、
sizeof
演算子で取得できる。x86およびx86_64環境では、int
は4、double
は8である。各変数ではこのサイズが十分入る分の領域が確保される。 - 変数は環境および型によって先頭のメモリが取れる位置が決まっており、
char
等1バイトの型を除けば、飛び飛びになる。これをアライメントといい、_Alignof
演算子(stddef.hをincludeしていればalignof
でも可能)で取得できる(_Alignof
はC11から)。x86およびx86_64環境では、int
は4、double
は8である。 - スタックへ変数は連続して詰まれるが、アライメントによりその場所に置けない場合は、置ける場所までずれる。
※ 質問のコードからちょっと変更を加えた物ですが、 https://godbolt.org/z/Ch2Uzw と https://godbolt.org/z/hKTXFR を見比べればわかると思います。int
はrbpとの差が4の倍数であれば置けますが、double
は8の倍数の所にしか置けないため、二つ目のコードのx
について[rpb-12]ではなく[rbp-16]になっています。 - 実際の所、x86_64のほとんどのコンパイラでは、ローカル変数毎にpushするのではなく、関数開始時にローカル変数が十分入る大きさのスタック領域を確保し、その領域に対してローカル変数を割り当てると言うことをしている。ただし、上から割り当てるのか、下から割り当てるのかはコンパイラによって異なる。GCCは上から下に向かって割り当てていく(pushで実装していた時代の名残と思われますが、ここら辺の歴史に詳しい人がいたら教えてください)。
※ 前の項でどちらも、変数毎にスタックへpushするのでは無く、最初に32バイトのスタック領域が確保するようにしているのがわかると思います。ただ、なぜ一つ目のコードが24バイトでは無く32バイトなのかはわかりません。x86_64でのスタック領域確保は16バイト単位とか縛りがあるんでしょうか?詳しい人教えてください。
上記のことからx86_64で、GCCで最適化オプション無しの場合(ただし一部のOSでは異なる場合がある)、上のアセンブリの結果も参考にすると、
a
はサイズ4なのでベースポインタ(関数がベースとするスタックの位置)より4少ない位置に配置される。この位置は4の倍数なのでアライメントも満たす。b
はサイズ4なのでa
より4少ない、ベースポインタより8少ない位置に配置される。この位置は4の倍数なのでアライメントも満たす。x
はサイズ8なのでb
より8少ない、ベースポインタより16少ない位置に配置される。この位置は8の倍数なのでアライメントも満たす。y
はサイズ8なのでx
より8少ない、ベースポインタより24少ない位置に配置される。この位置は8の倍数なのでアライメントも満たす。- 関数自体は上の処理を見越して、あらかじめ32(なぜ24ではないかは私にはわかりません)少ない位置にスタックポインタを進めている。
となります。異なるアーキテクチャ、OS、コンパイラ、コンパイラオプション、定義の順番が異なるコードでは違う動作になります。例えば、clangに変更しただけでも4,12,8という差になります。clangだと最初に4バイト確保しているようで、その分ズレて、x
の時にアライメントにあわせてズレてしまったため、b
とx
の差がGCCと異なったと言うことです。この最初の4バイトは何かというと・・・すいません、調べてもわかりませんでした。
以上ですが、わからないと言っているところばかりで申し訳ありません。もっと詳しいことはコンパイラを作れるぐらいのCとCPUに関する知識が無いと難しいと思います。残念ながら、私にはそこまでの知識は無いので、かいつまんだ内容になってしまい申し訳ないです。
投稿2019/10/17 12:23
編集2019/10/19 03:00総合スコア21733
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/18 00:15
2019/10/18 01:11
2019/10/18 01:14
2019/10/18 01:32
2019/10/18 12:06 編集
2019/10/19 11:52
0
番地がどのように増えていっているかを考察しないといけないのですが、
前提条件が少なすぎます。これ以外に、与えられたコードはないのですか?
(a
、b
、x
、y
が変数なのか、構造体のメンバなのか、はたまたそれ以外のものかすらわからない状況では、何も言えません。構造体のメンバは順番を入れ替えたり勝手に消したりが許されませんが、ローカル変数の場合は特に問題ありません)
投稿2019/10/16 08:36
総合スコア145121
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
回答としてはダメかもですが…
考察しないといけないのですが
という言い回しの雰囲気から察するに,
「授業(講義?)か何かで説明があった話に照らし合わせた範疇での」考察とやらをすればよい状況なのかな,という雰囲気を感じます.
いきなり何の事前説明もなく「おら,何でこうなるのか考えてみせろや!」とか無茶を言われているわけではないでしょうから,
例えば「スタック領域がどうの」的な説明等があったりしたのではないかな?と推測します.
そういった事前説明された際に出てきた言葉について調べてみるとよいのではないでしょうか.
投稿2019/10/16 06:41
総合スコア11632
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/17 03:02
2019/10/17 03:17
0
どのようなコードを書いて調べた結果か書かないと、考察しようがないですよ?
多分こうだろうという推察はできますが、必ずしもそれが正しいわけではないので。
投稿2019/10/16 03:18
編集2019/10/16 03:18総合スコア13703
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
コンパイラのマニュアルに変数の割付に関する規則が書いてありませんか?
基本的には、どの変数をどう割り当てるかはコンパイラの(コンパイラの作者の)自由です。
「こうなるのはなぜか?」の正解はコンパイラの作者に聞かなければ分かりません。
アセンブラで書いたプログラムとCで書いたプログラムをリンクするような場合を想定している
コンパイラなら、変数をどう割り付けるかをアセンブラレベルで説明してあると思います。
お使いのコンパイラでそのような説明を書いたマニュアルが見つからなければ、
「そういうもんだ」
と思う以外にありません。
投稿2019/10/16 05:42
総合スコア711
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
変数はメモリ上に配置されるのが原則です。そして&演算子は変数のアドレスを取得する演算子であることはご存じでしょう。
変数のアドレスが「増えて」いくのは、
- それぞれの変数がメモリ上に割り当てられているから
- それぞれの変数はメモリ上で必要なバイト数を必要とするから
です。
質問者は、表示結果をアドレス順に並べ直してみましたか?並べ直すと、こうなります。
yの番地は 0xffffcbe8 (~0xffffcbef)
xの番地は 0xffffcbf0 (~0xffffcbf7)
bの番地は 0xffffcbf8 (~0xffffcbfb)
aの番地は 0xffffcbfc (~0xffffcbff)
念の為:例えばこういった関係になってますね。
- 0xffffcbe8(yの番地)+ 8 = 0xffffcbf0(xの番地)
- 0xffffcbf8(bの番地)+ 4 = 0xffffcbfc(aの番地)
これは実は
- double 型の変数 y, x がそれぞれ8バイト(64bit)の領域を占めている
- int 型の変数 b, a がそれぞれ4バイト(32bit)の領域を占めている
- y, x, b, a がメモリ上で連続(密着)している
ことを示しています(括弧内に書き加えたアドレスは、各変数に割り当てられた、最後のメモリのアドレス)。
というのは、この8バイト、4バイトという値(大きさ)は、double, int という、それぞれの型の変数に必要なメモリの大きさだからです。これらの大きさは sizeof() で確認できますので、質問者も次のようなコードで確かめてみると良いです。
C
1 printf("size of double = %d\n", sizeof(double)); 2 printf("size of int = %d\n", sizeof(int));
変数 y, x, b, a は関数内のローカル変数なのでスタック領域に割り当てられているはずです。そのアドレスが0xffffcbe8~0xffffcbffなのだから、おおよそこの辺りがスタック領域となっているようだとわかります(スタックという言葉がわからなければコメントなり新たな質問をたてるなりしてください)。スタック領域がどこになるか、いつも同じではありません。コンパイラによって、OSによって、プログラムによって、様々な条件で大きく変わります。
注意すべきは y, x, b, a という順序はコンパイラが勝手に決めてよいことになっているので、常にこの順序になると思ってはいけない事です。この場合はたまたまそういう順序になったのです。
上の例は「密着」していますが、いつも密着しているとも限りません。~~この場合はたまたま密着したのです。~~この場合は、double型変数が2つ、int型変数が2つという組み合わせは密着が可能だということになります。密着しなかった=隙間ができた実例がZuihsinさんの回答にあります。ただ、あのコードなら必ず隙間ができるかというと、そうでもありません。質問者がご自分で試してみるとよいと思います。
もうひとつ簡単な実験をしてみるのも面白いと思います。それは変数を定義する順序を変えてみることです。例えば今
double x, y; int a, b;
となっている順序を
int a, b; double x, y;
としてみる、とかです。今、私の手元に3種類のCコンパイラがありますけど、
- 定義する順序を変えたらメモリ上の並び順が変わったコンパイラ
- 定義する順序を変えてもメモリ上の並び順が変わらないコンパイラ
があります。3つのうち2つはGCCですが(どちらもGCCなのに)この振る舞いが異なりました。ことほどさように、変数がメモリ上にどう配置されるかはコンパイラ次第です。アドレスは確かめてみないとわからないのです、もっともそんなことを気にしてプログラムを書くことは少ないですが。
投稿2019/10/17 04:42
編集2019/10/19 12:51総合スコア1380
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/10/17 04:49
2019/10/19 12:47
2019/10/19 12:52
2019/10/19 13:05
0
タグがCなのでC言語を前提にお話しします。
C言語の標準機能では、メモリアドレスを絶対番地で知ることや操作することはほぼ無理です(NULLとかは例外)。基準点からの相対値情報を用いることになります。
一方、基準点や刻み幅は状況によって決まります。つまりC言語の中だけでは具体的なことは定まりません。
例えばint型の配列int x[10]を使用すると。x[0], x[1], x[2]・・・x[9]に充たるメモリ領域が用意されます。これはx[0]を基準に追番が使用されますが、この基準となるx[0]のアドレス&x[0]はOSが割り振りますので指定できません。また、各要素領域の大きさも決められてはおりません。4バイトかもしれませんし8バイトかもしれません。
投稿2019/10/16 14:59
総合スコア4830
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。