質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.45%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++/CLI

C++/CLIは、.NET Frameworkの共通言語基盤であるCLI向けにC++を拡張したプログラム言語です。前身のC++マネージ拡張と比較するとシンプルで分かりやすい構文になっており、高い可読性を持ちます。

C++Builder

C++Builderは、C/C++を用いてアプリ開発できる統合開発環境 (IDE) 。DelphiのC++版です。コンポーネントによるビジュアル開発、高機能なコードエディターなどで生産性の高い開発ができます。

Visual Studio 2010

Microsoft Visual Studio 2010はMicrosoftが提供している統合開発環境(IDE)です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

6回答

12834閲覧

C++ memcpy コピー元バッファのメモリサイズとクラッシュする現象について

Taffy

総合スコア33

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

C++/CLI

C++/CLIは、.NET Frameworkの共通言語基盤であるCLI向けにC++を拡張したプログラム言語です。前身のC++マネージ拡張と比較するとシンプルで分かりやすい構文になっており、高い可読性を持ちます。

C++Builder

C++Builderは、C/C++を用いてアプリ開発できる統合開発環境 (IDE) 。DelphiのC++版です。コンポーネントによるビジュアル開発、高機能なコードエディターなどで生産性の高い開発ができます。

Visual Studio 2010

Microsoft Visual Studio 2010はMicrosoftが提供している統合開発環境(IDE)です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

0グッド

1クリップ

投稿2020/01/31 03:00

編集2020/02/10 01:46

↓ 下記の質問の結果について、掘り下げて質問させてください。
https://teratail.com/questions/238219

memcpyで、第二引数が第三引数で指定したコピーサイズより小さい場合、
プログラムがクラッシュするケースとしないケースとがあります。

C/C++

1char szString1[1024]; 2char szString2[512]; 3memcpy(szString1, szString2, 1024);

クラッシュしたときのダンプファイルを見ると、
「保護されているメモリに読み取りまたは書き込み操作を行おうとしました。他のメモリが壊れていることが考えられます。」というメッセージが出ていました。

そこで質問なのですが、クラッシュするケースでは保護されているメモリ空間(他によって確保されている空間など)に不正にアクセスしたから、クラッシュしたという理解をしているのですが、合っていますでしょうか?

他の人から、「C++Builder6の場合は、このようなプログラムを書いてもクラッシュしなかった」と言われ、違いを説明できませんでした。
(今回は、Visual Studio 2010 Professionalを使って開発をしています)
そもそも、確保していないメモリサイズを超えたサイズ指定をするようなコードを書くこと自体が間違いなのですが
クラッシュする/しない という現象の違いは、
・動作時のメモリ空間の違い なのか
・コンパイラの違い なのか
考えられるケース、起きる理屈について教えていただけないでしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

メモリレイアウトの自由と OS による管理

メモリのレイアウトがどうなっているかは言語仕様に定めがなく、処理系の裁量によります。 アクセスした箇所が別の用途に使われていたら矛盾を引き起こしてデタラメなことになることもあるでしょうし、偶然にも何も使われていないところかもしれません。

アプリケーションから見える「メモリ空間」に対して物理メモリを割り当てるのは OS の役割です。 きちんとした手順で確保していないメモリにアクセスした場合にはそれを検知できます。 アクセスした場所がそういう場所であればわかりやすく検出してプロセスを即死させることもあります。

様々な理由の組み合わせによって起こるので、何が起こっているか正確に知るにはそのプログラムを観察するしかありません。

有名どころのデバッガとしては

  • OllyDbg
  • GDB
  • Immunity Debugger

といったものがあるので、これらの使い方を学んでみてはどうでしょうか。 実行の様子を観察するのは楽しいですよ。

未定義動作

C や C++ の言語仕様としては確保してないメモリにアクセスにアクセスした場合の挙動は未定義です。 書き込みでも読み込みでもです。

C++ における「未定義動作」というのは非常に影響力が強く、未定義動作としていることをやってしまったらコンパイラはどのような結果を生んでも良いことになっています。 しかも未定義動作をした箇所よりずっと後やずっと前に不可解な動作を引き起こしてもかまいません。 (俗に「タイムトラベル」と呼ばれることもある現象です。)

特に強力な最適化機構を持つコンパイラは未定義がどうなってもかまわないことを積極的に利用して考えられる中で最も速い処理を選ぶことがあります。 単に周辺のコードをまるごと除去することもありますし、容赦なくデタラメな動作に突入することもあります。

超複雑で多数の様々な最適化機構のコンボで意味不明のことが起こるのはよくあることなので C++ では未定義動作に意味のある解釈を試みるのはあまり意味がありません。 前後の状況との組み合わせによっても変わってくるので、考えるだけ無駄です。

投稿2020/01/31 04:43

編集2020/01/31 04:44
SaitoAtsushi

総合スコア5466

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

C/C++ は良くも悪くも融通が利くので、確保したメモリ領域を超えてアクセスしたとしても、何らかの対処をしてくれません。
超えた場合にどうなるかは環境依存(CPU、OS、コンパイラ、コンパイル時のオプションなど)になるので、常にどうなる、とは言えません。

例えばですが、メモリを16byteだけ確保しようとしたとしても、OSのメモリ管理の都合で実は1KBが割当たってしまうこともあり得ます。そうなると16byteといいながら、1KB使っても文句を言われません。
一方、律儀に16byteだけ確保するようなOSだとしたら、17byte目をアクセスしようとした途端に(OSで)アクセス違反の例外が発生する(そしてクラッシュする)場合も当然ながらあります。

投稿2020/01/31 04:54

tacsheaven

総合スコア13703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

・コンパイラの違い なのか

Visual Studio の場合スタックが破壊または改竄されたことを検知するための実行コードが埋め込まれたりします。
その検知に引っかかるとクラッシュします。
GuardStack (GS)

また、完全にメモリアクセス違反を起こしているとコンパイラが判断した場合、ソースコードを完全に無視して例外をthrowする実行コードが生成されたります。

なぜクラッシュするのか、なぜクラッシュしないのかを確認するにはアセンブリを確認したりデバッガを使って動作を確認する必要があります。

投稿2020/02/08 13:03

編集2020/02/08 13:05
hmmm

総合スコア818

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rubato6809

2020/02/09 00:52

ほう、まだ具体的な仕組みが飲み込めてませんが、面白い。 Visual Studio である、且つ、(少なくとも)コピー元配列がスタック上に取られる、且つ/GSスイッチ有効でコンパイルされると、クラッシュするかも、ということですね。 1.コンパイラがVisual Studio であるかどうか(そうでなければこの話は無し)。 2. 質問者が/GSスイッチの有無が現象の発生と一致するか、を確かめればはっきりするんじゃないかな。というか、Visual Studio 使える人なら誰でもすぐ確かめられるか。
hmmm

2020/02/10 01:08

あー、きちんと見てませんでした。 writeじゃなくてread時のアクセス違反ならGSじゃないですね。
Taffy

2020/02/10 01:45

1.コンパイラはVisual Studio 2010を使用しています。 当該の処理はDLL側のプロジェクトに書かれており、548バイト以降はメモリ保護がかかっている部分にアクセスしているようです。(この点はほかの人が確認しただけで、当方ではまだ未確認です。)
rubato6809

2020/02/10 01:54

> read時のアクセス違反ならGSじゃない > 548バイト以降はメモリ保護がかかっている部分にアクセス なら、私の見立て通りなのでは。
Taffy

2020/02/10 07:36

DLLのGSスイッチを切替えて動 作させてみました。 有効にしたとき(デフォルト)…発生する 無効にしたとき…発生しない このことから、GSが有効となっていることが関係しているのではないかと思いましたが Read時のアクセス違反だと、GS関係がないのでしょうか? GSスイッチの切り替えについては、以下のサイトを参考にして実施しました。 https://docs.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check?view=vs-2019
hmmm

2020/02/10 13:02

/GSはスタックの破壊(Write)を検知するものです。 > 当該の処理はDLL側のプロジェクトに書かれており、 Visual Studio 2010で再現可能な最小のソースコードとプロジェクト(コンパイルの設定)もしくはアセンブリなどを提示してもらわないと確認不能です。 https://teratail.com/questions/238219 こちらのソースコードを見てみましたがヒープ上のメモリにアクセスしているので、こちらで提示されているソースコードとは状況が異なります。
guest

0

経験的に言うと(具体的なアドレスなどはともかく)原因も違いも明確です。例外が発生するかしないかの違いは、コピーのソース側となっている配列 char szString2[512]; の配置場所(メモリアドレス)に依ります。もう少し言うと、この512バイトのメモリに続く残り512バイトのメモリ領域の中に、アクセス許可されている(=保護されていない)メモリと、許可されていない(=保護されている)メモリの境界があったという事です。境界が残り512バイトのメモリ領域の中に無ければクラッシュしません。

クラッシュするケースでは保護されているメモリ空間に不正にアクセスしたから、クラッシュした

は正しい理解です。念の為に繰り返しますが、アクセス許可されているメモリとは、保護されていないメモリ、ですよ。

書き込む先のメモリ領域にバッファオーバーランして書き込んだら、他の用途に使っているメモリ(他の変数)を上書きするので、その後、プログラムが異常動作する事は容易に想像できると思います。
一方、ご質問の状況は読み出し側のバッファオーバーランです。通常、コピー元のメモリ領域は、読みだす分にはオーバーランアクセスしたって(メモリを書き変えないのだから)問題を起こすことは無いものです。従って、問題なく動作することは少なくありません。というか、読み出しだけならオーバーランアクセスしても何も問題を起こさないでしょう。

ところがクラッシュするケースがある、それは例外(メモリアクセス例外、OSやCPUによって具体的な呼び名は違うかもしれない)が起こったことを意味します。そうなる理由はただひとつ、残り512バイトのメモリ領域の、ある番地から、アクセスが許可されていない・保護されたメモリだった、ということに尽きます。

プログラムが動作する時、動作に必要なメモリを、OSはアクセス可能なメモリ領域として割当てます。現在のCPUはメモリ管理ユニット、略してMMUを内蔵しており、OSはMMUを制御することで、各プロセスがアクセスできるメモリに制限をかけています。アクセス制限されたメモリ領域をアクセスしようとすれば(プログラムの異常動作であると見做して)例外を発生させ、プログラムを停止するようにしています。それがクラッシュするという事です。

そのアクセスを許可したメモリ領域の中に szString1[1024] も szString2[512]も配置されますが、szString2[512] に続く、残りの512バイトがアクセス可能なメモリである保証はありません。アクセスできたなら、それは運が良かったわけです。szString2[512]をメモリのどこに配置するか、なぜ境界の近くに配置されたのか、はコンパイラと、OSの、両方の事情の合わせ技のようなもので決まります。お使いのOSとコンパイラ次第なので具体的な説明はできかねますが。

memcpy() の中身はこんな感じです(愚直に書いてみた、ツッコミ無用笑)。

C

1void *memcpy(void *buf1, const void *buf2, size_t n) 2{ 3 unsigned char *d = buf1; 4 unsigned char *s = buf2; 5 unsigned char temp; 6 7 while (n > 0) { 8 temp = *s; // コピー元のメモリを読む 9 *d = temp; // コピー先のメモリに書く 10 ++s, ++d, --n; 11 } 12 return buf1; 13}

「境界」を超えた時点で「temp = *s; // コピー元のメモリを読む」というメモリアクセス命令が

「保護されているメモリに読み取り(中略)操作を行おうとしました・・・」

即ち不正アクセスになり、例外を起こしたのです。

こうしたことを実感するには、各変数、配列などのメモリアドレスを確認するなどして、具体的なメモリ上の配置を調べてみるとよいです。また変数の配置は、コンパイラ(実際はリンカ)の仕様を調べると共に、コンパイル時にリンケージマップ情報を出力させて調べることができます。さらに、荒っぽい話に聞こえるかもしれませんが、わざとバッファーオーバーランアクセスを起こしてみて、どこで例外が起こるか試してみる、なんていう調べ方もできます。

投稿2020/02/04 01:16

編集2020/02/04 13:53
rubato6809

総合スコア1382

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

そもそも、確保していないメモリサイズを超えたサイズ指定をするようなコードを書くこと自体が間違いなのですが

そのとおりです。範囲を超えたアクセスは未定義の動作になって、何1つ保証されません。もともとメモリに置かれていたデータにすら影響を受けたとしても、文句は言えません。

投稿2020/01/31 04:09

maisumakun

総合スコア145208

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

maisumakun

2020/01/31 04:10

> 「C++Builder6の場合は、このようなプログラムを書いてもクラッシュしなかった」 運が良かったとか悪かったとか、そういうレベルの話です。コンパイルオプションや実行環境を変えれば変わってしまうこともあります。
Taffy

2020/02/03 00:51

私も感覚的には「"たまたま"そのコンパイラではうまく動いていただけなのでは?」と思いうまく説明できなかったのですが、未定義の動作であり、何一つ保証されないものである、という説明ができそうです。どうもありがとうございます。
guest

0

変数として確保されたエリア以外のメモリ空間にアクセスしようとしたからエラーとなったってことです
これは、
・CPUの違い
・OSの違い
・動作時のメモリ空間の違い
・コンパイラの違い
のすべてが影響する話ですね

投稿2020/01/31 04:06

y_waiwai

総合スコア87802

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.45%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問