メモリ領域の確保について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
elga-c

メモリ領域の確保について

#1

投稿記事 by elga-c » 10年前

はじめまして。よろしくお願いします。
変数宣言した時にメモリがどのように確保されるのか調べてます。

以下ソース(注:メモリ不正アクセスの不具合があります。)を実行した時、str_Bの先頭バイトが破壊される事象が発生しました。
降順かつ連続した領域に確保されるとデータが破壊されます。(例えばstr_Aが41~80番地、str_Bが1~40番地)
発生したのはSolaris環境ですが、環境が無いため準備出来るWindowsで検証をしていますが再現出来ません。

質問
・VS2008ではアクセス違反コードが有る時と無い時で確保方法が異なるのは何故なのでしょうか?(違反有り:非連続、違反無し:連続)
・アクセス違反部分を削除し実行するとVS2012+x64ビルドだけ降順になるのは何故なのでしょうか?
・VS2012ではアクセス違反部分があると実行エラーになるのは何故なのでしょうか?

コード:

#include <stdio.h>
#include <string.h>

void main(void)
{
  char str_A[40];
  char str_B[40];

  FILE *fp;
  fp = fopen("debug.txt","w");

  fprintf(fp, "str_A[1] =%p\n", &str_A[0]);
  fprintf(fp, "str_A[%d]=%p\n", sizeof(str_A), &str_A[sizeof(str_A)-1]);
  fprintf(fp, "str_B[1] =%p\n", &str_B[0]);
  fprintf(fp, "str_B[%d]=%p\n", sizeof(str_B), &str_B[sizeof(str_B)-1]);

  str_A[40] = '\0'; /* アクセス違反 */
  memcpy(str_A,"0123456789012345678901234567890123456789",40);
  str_B[40] = '\0'; /* アクセス違反 */
  memcpy(str_B,"0123456789012345678901234567890123456789",40);

  fclose(fp);

}
以下は検証結果です。

検証環境:Windws7(x64) + VisualStudio
実行環境:検証環境と同じ

■上記コードからアクセス違反部分とmemcpy部分の4行を削除し変数宣言とfprintfだけにして実行した結果
・VS2008、ビルド構成マネージャ:Release + Win32 → 結果:昇順+連続
str_A[1] =003AFC38
str_A[40]=003AFC5F
str_B[1] =003AFC60
str_B[40]=003AFC87
・VS2008、ビルド構成マネージャ:Release + x64 → 結果:昇順+連続
str_A[1] =00000000001DFB00
str_A[40]=00000000001DFB27
str_B[1] =00000000001DFB28
str_B[40]=00000000001DFB4F
・VS2012、ビルド構成マネージャ:Release + Win32 → 結果:降順+連続 ※
str_A[1] =0018FA20
str_A[40]=0018FA47
str_B[1] =0018F9F8
str_B[40]=0018FA1F
・VS2012、ビルド構成マネージャ:Release + x64 → 結果:昇順+連続
str_A[1] =00000000002AF6D0
str_A[40]=00000000002AF6F7
str_B[1] =00000000002AF6F8
str_B[40]=00000000002AF71F

■上記コードを実行した結果(アクセス違反コード有り)
・VS2008、ビルド構成マネージャ:Release + Win32 → 結果:昇順+非連続
str_A[1] =003DFEAC
str_A[40]=003DFED3
str_B[1] =003DFED8
str_B[40]=003DFEFF
・VS2008、ビルド構成マネージャ:Release + x64 → 結果:昇順+非連続
str_A[1] =00000000002AF7C0
str_A[40]=00000000002AF7E7
str_B[1] =00000000002AF7F0
str_B[40]=00000000002AF817
・VS2012、ビルド構成マネージャ:Release + Win32 → 実行エラー
・VS2012、ビルド構成マネージャ:Release + x64 → 実行エラー

よろしくお願いします。

elga-c

Re: メモリ領域の確保について

#2

投稿記事 by elga-c » 10年前

すみません。破壊されるのはstr_Bの先頭バイトではなくstr_Aの先頭バイトでした。
失礼しました。

box
記事: 2002
登録日時: 14年前

Re: メモリ領域の確保について

#3

投稿記事 by box » 10年前

elga-c さんが書きました: 変数宣言した時にメモリがどのように確保されるのか調べてます。
ということであれば、

コード:

    printf("%p\n", str_A);
    printf("%p\n", str_B);
ですむのではないでしょうか。
値の小さい方を低位アドレス、大きい方を高位アドレスに割り当ててあるはずです。

ちなみに、当方のWindows10で動かしてみたら、str_Aの方を低位アドレスに割り当てました。
Solarisでstr_Aの先頭を破壊していることがわかっているのであれば、

コード:

    str_B[40] = '\0';
のように配列str_Bの領域範囲外にアクセスした時点で、str_A[0]に相当する部分を壊していると考えられます。
したがって、SolarisではWindowsとは異なり、str_Bの方を低位アドレスに割り当てているのでありましょう。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

きゃりーわんわん
記事: 34
登録日時: 12年前

Re: メモリ領域の確保について

#4

投稿記事 by きゃりーわんわん » 10年前

アドレスが非連続なのは、おそらく最適化の結果、各配列の先頭アドレスが
4または8の倍数となるようにパディングされたからだと思います。

P.S.
ローカル変数はスタックに格納されると思い込んでいました。
全ての実装系で保障されていたわけではないのですね。
勉強になりました。

Bull
記事: 149
登録日時: 11年前

Re: メモリ領域の確保について

#5

投稿記事 by Bull » 10年前

規格ではオブジェクト(変数・配列)をどのように確保
するかは規定されていませんので、あまり気にする必要
は無いと思います。コンパイラーによって違いが出てく
ると思われますが、このような不正アクセスのケースで
は、コンパイラーがどのようにオブジェクトを配置する
かを把握しているとデバッグがしやすいかも知れません。

VCで例外が発生するのは/GSオプションでセキュリティの
チェックが組み込まれているためのようです。コンパイル
結果を見てみると、

コード:

  str_A[40] = '\0'; /* アクセス違反 */
  memcpy(str_A,"0123456789012345678901234567890123456789",40);
  str_B[40] = '\0'; /* アクセス違反 */
  memcpy(str_B,"0123456789012345678901234567890123456789",40);
の部分のコードは生成されていません。最適化により必要
ないと判断されたようです。その後で例外を出すコードが
埋め込まれています。

また、おっしゃるように配列の領域外にアクセスすると
配列の間に隙間が開きますが、これはコンパイラが不正
アクセスを感知しての処置と思われます。しかし正確な
事はコンパイラー製作者でないとわからないでしょう。

elga-c

Re: メモリ領域の確保について

#6

投稿記事 by elga-c » 10年前

皆さん回答ありがとうございます。

boxさん>
Windows10で確認いただきstr_Aが低位アドレスに割り当てられたとのことですが、コンパイラは何を使用したか教えていただけますか?
不具合を解消しstr_A[41]、str_B[41]として実行してみました。
するとやはりVS2008+x86ビルド、VS2008+x64ビルド、VS2012+x64ビルドはstr_Aが上位アドレスとなり、VS2012+x86ビルドだけstr_Aが低位アドレスになりました。
不具合解消している場合はmemcpy、\0代入の式があっても確保する領域に変化はありませんでした。
このVS2012+x86ビルドの動作は何か正式な仕様として公表されていないでしょうか?

きゃりーわんわんさん>
4で割り切れない数(41で検証)にするとx86では4バイト、x64では8バイト単位でパディングされること確認できました。
“おそらく最適化の結果”という部分の明確な仕様はMSとかで公表されてないでしょうか?VCに限った話で構いませんので
変数宣言だけの場合は(4で割り切れる場合)連続して確保するが、アクセス違反の構文があったらx86で4バイト、x64で8バイト飛ばして確保する動作仕様を明確にしたいのです。

Bullさん>
/GSオプションですがVC2008にもあるようで、バッファセキュリティチェックはTrueになっていることを確認しました。
ただそれでもVC2008では実行出来てしまい、VC2012では実行エラーになるのは何故なのでしょうか?
なにか/GSの機能がVC2012により厳しくなるようなことがあったのでしょうか?

抽象的な質問になり申し訳ありません。
すでに不具合を含んだアプリが動作しており、少なくとSolaris環境では重要部分(str_Aの先頭バイト)を破壊してしまいました。
様々な環境で動作しているアプリのため、その影響範囲を可能な限り正確な情報(メーカーから発表の動作仕様等)で把握したいのです。
自分で出来る検証をもう少し進めてみますので、もうしばらくインシデントはオープンさせてください。

よろしくお願いします。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 14年前
住所: 東海地方
連絡を取る:

Re: メモリ領域の確保について

#7

投稿記事 by softya(ソフト屋) » 10年前

どのコンパイラ・リンカもそうですが、コンパイル時のオプション指定・最適化の動作(ソースコード次第)などでメモリ上の配置は変化すると思います。
私の知る限り、最適化レベルとメモリのアライメントはオプションで変更出来ますが、記憶クラスが同一な変数・構造体変数の最適化後のメモリ配置を指定するオプションは存在していなかったと思います。そのレベルでのメモリ配置の資料も存在を知りません。
メモリアドレス自体がコンパイラ・バージョンとリンカのバージョンでも変化する可能性が高いです。ソースコードを少し変えただけでも変化する可能性があります。
C++の規格外の部分ですので、正式資料自体ない可能性が高いと思います。

デバッグのために特定環境でのメモリ配置などを調べるのは意味がある調査だと思いますが、様々な環境で稼働中のアプリのバグの出ない事を証明することは事実上困難ではないでしょうか。 全部のOS、コンパイラバージョン、コンパイルオプションなどの組み合わせを全て網羅する以外に手が無いです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

Bull
記事: 149
登録日時: 11年前

Re: メモリ領域の確保について

#8

投稿記事 by Bull » 10年前

elga-c さんが書きました:/GSオプションですがVC2008にもあるようで、バッファセキュリティチェックはTrueになっていることを確認しました。
ただそれでもVC2008では実行出来てしまい、VC2012では実行エラーになるのは何故なのでしょうか?
なにか/GSの機能がVC2012により厳しくなるようなことがあったのでしょうか?
VC++ 2008でエラーにならない理由ですが、前にも書きましたが、最適化すると不正アクセスするコードは実際には生成されません。ですので、main()の最後のセキュリティのチェックに引っかからないという事ですね。最適化をやめるとエラーになります。
では、なぜVC++ 2012でエラーが出るかと言いますと、エラーチェックせずに例外を発生させているからです。プログラムソース上は明らかに不正アクセスを行っているので、コンパイラが問答無用で、エラーにしているのでしょう。

閉鎖

“C言語何でも質問掲示板” へ戻る