3

私はこのコードを持っています、そしてそれは完全にうまく動きます、そして私は理由がありません:

int main(){
   int len = 10;
   char arr[len];
   arr[150] = 'x';
}

真剣に、それを試してみてください!それは(少なくとも私のマシンでは)動作します!ただし、インデックス20,000など、インデックスが大きすぎる要素を変更しようとすると機能しません。したがって、コンパイラは明らかに、その1行を無視するほど賢くはありません。

では、これはどのように可能ですか?私はここで本当に混乱しています...


さて、すべての答えに感謝します!

したがって、これを使用して、スタック上の他の変数によって消費されるメモリに次のように書き込むことができます。

#include <stdio.h>
main(){
   char b[4] = "man";
   char a[10];
   a[10] = 'c';
   puts(b);
}

「缶」を出力します。それは本当に悪いことです。

わかりました、ありがとう。

4

6 に答える 6

4

C コンパイラは通常、効率のために、配列の範囲をチェックするコードを生成しません。範囲外の配列アクセスは「未定義の動作」を引き起こし、考えられる結果の 1 つは「動作する」ことです。クラッシュやその他の診断の原因となる保証はありませんが、仮想メモリをサポートするオペレーティング システムを使用していて、配列インデックスが物理メモリにまだマップされていない仮想メモリの場所を指している場合、プログラムはより効果的です。クラッシュする可能性があります。

于 2013-03-07T01:34:13.013 に答える
3

では、これはどのように可能ですか?

スタックがマシン上で十分に大きいため、たまたま対応する場所のスタックにメモリの場所があり&arr[150]、小さなサンプルプログラムが他のプログラムがその場所を参照する前に終了し、おそらくクラッシュしたためです。上書きしました。

使用しているコンパイラは、配列の終わりを超えようとする試みをチェックしません(C99仕様ではarr[150]、サンプルプログラムの結果は「未定義」であるため、コンパイルに失敗する可能性がありますが、ほとんどのCコンパイラはそうではありません)。

于 2013-03-07T01:39:26.467 に答える
2

ほとんどの実装では、この種のエラーはチェックされません。多くの場合、メモリ アクセスの粒度は非常に大きく (4 KiB 境界)、きめ細かいアクセス制御のコストは、既定では有効になっていないことを意味します。エラーが最新の OS でクラッシュを引き起こす一般的な方法は 2 つあります。マッピングされていないページからデータを読み書きする (インスタント segfault) か、別の場所でクラッシュを引き起こすデータを上書きするかのいずれかです。運が悪ければ、バッファ オーバーランはクラッシュせず (そうです、不運です)、簡単に診断することはできません。

ただし、インストルメンテーションをオンにすることはできます。GCC を使用する場合は、Mudflap を有効にしてコンパイルします。

$ gcc -fmudflap -Wall -Wextra test999.c -lmudflap
test999.c: 関数 'main' 内:
test999.c:3:9: 警告: 変数 'arr' が設定されていますが、使用されていません [-Wunused-but-set-variable]
test999.c:5:1: 警告: 制御が非 void 関数の最後に達しました [-Wreturn-type]

実行すると次のようになります。

$ ./a.out
********
マッドフラップ違反 1 (チェック/書き込み): time=1362621592.763935 ptr=0x91f910 size=151
pc=0x7f43​​f08ae6a1 location=`test999.c:4:13 (メイン)'
      /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_check+0x41) [0x7f43​​f08ae6a1]
      ./a.out(メイン+0xa6) [0x400a82]
      /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x7f43​​f0538ead]
近くのオブジェクト 1: チェックされた領域は 0B で始まり、141B で終わります
マッドフラップ オブジェクト 0x91f960: name=`アロカ リージョン'
bounds=[0x91f910,0x91f919] size=10 area=heap check=0r/3w liveness=3
割り当て時間 = 1362621592.763807 pc = 0x7f43​​f08adda1
      /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_register+0x41) [0x7f43​​f08adda1]
      /usr/lib/x86_64-linux-gnu/libmudflap.so.0(__mf_wrap_alloca_indirect+0x1a4) [0x7f43​​f08afa54]
      ./a.out(メイン+0x45) [0x400a21]
      /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xfd) [0x7f43​​f0538ead]
近くのオブジェクトの数: 1

ああ、それはクラッシュしました。

Mudflap は完璧ではないことに注意してください。すべてのエラーをキャッチできるわけではありません。

于 2013-03-07T02:04:32.547 に答える
1

ネイティブ C 配列は境界チェックを受けません。これには、追加の命令とデータ構造が必要になります。C は効率とリーンネスを重視して設計されているため、安全のためにパフォーマンスを犠牲にする機能は指定されていません。

valgrind のようなツールを使用できます。これは一種のエミュレーターでプログラムを実行し、どのバイトが初期化され、どのバイトが初期化されていないかを追跡することで、バッファー オーバーフローなどを検出しようとします。しかし、たとえば、オーバーフローしたアクセスがたまたま別の変数への正当なアクセスを実行した場合など、絶対確実ではありません。

内部的には、配列のインデックス付けは単なるポインター演算です。と言うとき、特定のオブジェクトのアドレスを取得するために、 1 つの要素arr[ 150 ]の 150 倍を追加し、それを のアドレスに追加するだけです。そのアドレスは単なる数字であり、ナンセンス、無効、またはそれ自体が算術オーバーフローである可能性があります。これらの条件のいくつかは、アクセスするメモリが見つからない場合やウイルスのような活動を検出できない場合に、ハードウェアがクラッシュを生成する結果となりますが、ソフトウェア フックの余地がないため、ソフトウェアによって生成された例外は発生しません。安全な配列が必要な場合は、加算の原則に基づいて関数を構築する必要があります。sizeofarr

ちなみに、あなたの例の配列は技術的にも固定サイズではありません。

int len = 10; /* variable of type int */
char arr[len]; /* variable-length array */

constオブジェクトを使用して配列サイズを設定することは、C99 以降の新機能です。関数パラメーター、ユーザー入力などにすることもできlenます。これは、コンパイル時の分析に適しています。

const int len = 10; /* constant of type int */
char arr[len]; /* constant-length array */

完全を期すために: C 標準では境界チェックは指定されていませんが、禁止されているわけでもありません。これは、未定義の動作、またはエラー メッセージを生成する必要のないエラーのカテゴリに分類され、影響を与える可能性があります。セーフ配列を実装することは可能であり、機能のさまざまな近似が存在します。C、たとえば、配列 B から任意のオブジェクト A にアクセスするための正しい範囲外インデックスを見つけるために、2 つの配列の違いを取ることを違法にすることによって、この方向にうなずきます。しかし、この言語は非常に自由です。 A と B が同じメモリ ブロックの一部であるmalloc場合は有効です。つまり、C 固有のメモリ トリックを使用すればするほど、C 指向のツールを使用しても自動検証が難しくなります。

于 2013-03-07T02:16:48.677 に答える
0

C 仕様では、配列の末尾を超えて要素にアクセスすることは未定義の動作です。未定義の動作とは、仕様が何が起こるかを述べていないことを意味します。つまり、理論上は何でも起こり得るということです。プログラムがクラッシュするかもしれないし、そうでないかもしれないし、数時間後に完全に関係のない機能でクラッシュするかもしれないし、ハードドライブを消去するかもしれない (運が悪くて、適切なビットを適切な場所に突き刺した場合)。

未定義の動作は簡単には予測できず、絶対に依存すべきではありません。何かが機能しているように見えるからといって、それが未定義の動作を引き起こした場合、それは正しいとは言えません。

于 2013-03-07T01:42:44.560 に答える
0

あなたは幸運だったからです。というか、バグを見つけるのが難しくなるので、不運です。

ランタイムは、別のプロセスのメモリ(または場合によっては未割り当てメモリ)の使用を開始した場合にのみクラッシュします。アプリケーションを開くと、一定量のメモリが与えられます。この場合はこれで十分です。自分のメモリを好きなだけいじることができますが、デバッグ ジョブの悪夢に悩まされることになります。

于 2013-03-07T01:42:48.267 に答える