17

私の C コンパイラ (gcc) では、次のようなことができることに気付きました。

#include <stdio.h>
main(){
    short m[32768];
    short y = -1;
    short z = -1;
    printf("%u\n", y);
    m[y] = 12;
    printf("%d\n%d\n", y, m[z]);
}

実行すると吐き出します:

4294967295
12
12

これは私には少し不可解に思えます。

まず、このようなプログラムを実行しても安全ですか? 誤ってオペレーティング システムを上書きしてしまう可能性はありますか (関連する場合に備えて OS X を実行しています)。

また、過去に遭遇したような segfault エラーの少なくともいくつかの種類を期待していましたが、このようなエラーを静かに無視することは本当に怖いです。このプログラムがセグメンテーション違反を起こさないのはなぜですか?

そして最後に、好奇心から (これは最もばかげた質問かもしれません)、狂気への方法はありますか? すべての ANSI C コンパイラがこのように動作すると期待できますか? 異なるプラットフォームでの gcc はどうですか? メモリのレイアウトは悪用可能であると明確に定義されていますか (クロスプラットフォームの難読化されたコードを作成する場合など)?

4

6 に答える 6

15

C言語は、特定のプログラムの動作を「未定義」として定義します。彼らは何でもできます。そのようなプログラムを誤ったものと呼びます。

それらの1つは、配列の宣言/割り当てられた境界の外側にアクセスするプログラムです。これは、プログラムが非常に注意深く行います。

あなたのプログラムは間違っています。あなたの誤ったプログラムがたまたま行うことはあなたが見るものです:-}それは「OSを上書きする」可能性があります。実際問題として、ほとんどの最新のOSはそれを実行できませんが、プロセススペースの重要な値を上書きする可能性があり、プロセスがクラッシュ、停止、またはハングする可能性があります。

簡単な答えは、「間違ったプログラムを書かないでください」です。次に、表示される動作は「C」に意味があります。

この特定のケースでは、特定のコンパイラで、「一種の」配列インデックスが機能します。配列の外部でインデックスを作成すると、何らかの値が取得されます。mに割り当てられたスペースは、スタックフレーム内にあります。m [0]はスタックフレーム内のある場所にあり、配列アドレスとインデックスを組み合わせたマシン演算に基づく「m [-1]」も同様であるため、セグメンテーション違反は発生せず、メモリ位置にアクセスします。これにより、コンパイルされたプログラムは、そのメモリ位置を誤ったプログラムとして読み書きできます。基本的に、コンパイルされたCプログラムは、配列アクセスが範囲外であるかどうかを確認しません。

このプログラムに適用された場合、CheckPointerツールは、実行時に配列インデックスが不正であることを通知します。したがって、プログラムを自分で目で確認して間違いがないかどうかを確認するか、間違いがあったときにCheckPointerに通知させることができます。いずれにせよ、目を見張ることを強くお勧めします。

于 2012-05-28T02:01:44.620 に答える
3

INRIA のCompCert Cに興味があるかもしれません。これは、形式的に、数学的に検証可能で、検証済みの C 言語の実装です。有名なCoq 証明アシスタントと同じ作者です。別のバリアント Verifiable Cもあります。

私はそれについてあまり知りませんが、フランスの飛行機エンジニアが飛行機に搭載される予定の組み込みコンピューターをプログラムするためにそれを使用していることを知っています.

最後に、正式に検証可能な言語は安全な言語とは異なることに注意してください。

例えば ​​MISRA C は安全な C 言語と言われていますが (議論はありますが)、Safe-Cや Microsoft のChecked-CCycloneもあり、コンパイラを変更しないSafe C Librarylibsrtなどの安全なライブラリもあります。 、または標準のコンパイラとライブラリを使用するだけで、frama-cなどのソースコードアナライザーを使用します。

ただし、安全な言語はバッファ オーバーフローなどのいくつかの問題を修正しますが、重要なシステムに必要な一貫したロジック フローを保証するものではありません。たとえば、CompCert C は、同じ C 命令に対して常に同じアセンブラー命令セットを生成する必要があります。CompCert C や Ada などの形式的に検証可能な言語は、そのような形式的な保証を提供します。

これらの記事にも興味があるかもしれません:

于 2019-10-26T01:12:23.830 に答える
2

まず、このようなプログラムを実行しても安全ですか?

あなたの例: いいえ。なぜあなたも試してみますか?あなたはそれが何をすることを期待していますか?負のインデックスを使用したより一般的な例 - 正当なメモリに逆参照する限り、問題ありません。

また、過去に遭遇したような segfault エラーの少なくともいくつかの種類を予想していましたが、このようなエラーを黙って無視するのは本当に怖いです。このプログラムがセグメンテーション違反を起こさないのはなぜですか?

盲目的な運。(実際には正確ではありません-Ira Baxterによってうまく説明されています)

そして最後に、好奇心から (これは最もばかげた質問かもしれません)、狂気への方法はありますか?

配列内のものへのポインターを設定すると、負のインデックスが機能する可能性がありますが、他の人が理解して維持するのは悪夢です! - 組み込みシステムで行われているのを見たことがあります。

すべての ANSI C コンパイラがこのように動作すると期待できますか?

うん。

異なるプラットフォームでの gcc はどうですか?

はい

メモリのレイアウトは悪用可能であると明確に定義されていますか (クロスプラットフォームの難読化されたコードを作成する場合など)?

ええ、でもあなたが本当にそれに頼りたいかどうかはわかりません。

于 2012-05-28T02:09:26.327 に答える
1
  • OS に損害を与える可能性があるという観点からは、「安全」です。ほとんどの場合、ほとんどの「最新の」オペレーティング システムは、なんらかの形式の「ストレージ保護」を実装しているため、わがままなプログラムや悪意のあるプログラムが OS や他のプログラムに悪いことを行うことはできません。(しかし、「ほぼ」というのは、50 行を超えるソフトウェアは完璧ではなく、鎧に隙間が見つかる可能性が常にあることを示しています。)
  • プログラムが適切に「動作」するという観点から「安全」ですか?まあまあまあ、いや、ほとんど。ここで「ほとんど」とは、知識のある (「賢い」とは言いません) プログラマーが、コンパイラーとランタイムがどのように動作するかを知っていて、効率的に実行する必要がある場合に、負の配列インデックスを使用するなどの「奇妙な」ことを時折行うという事実に関連しています。やや標準から外れた何かを達成する. しかし、彼らは(願わくば)彼らが使用している「トリック」がシステム/コンパイラに大きく依存していることを知ってこれを行います。

しかし、@jb が示したように、「安全な C プログラミング」は矛盾した表現です。

于 2012-05-28T02:31:15.450 に答える
0

上記に加えて、valgrind に対して実行されるプログラムは、コンパイラと OS の信頼のみに基づいて解き放たれるプログラムよりも、エラーとして検出される可能性がはるかに高いことをお勧めします。Valgrind はすべてをキャッチするわけではありませんが、初期化されていないメモリや範囲外のメモリへのアクセスを適切に検出します。

于 2012-05-28T03:38:03.853 に答える
-1

プログラムがメモリ内の正当なアドレスを要求した場合(動作が定義されていないため、m [-1]実行する可能性があります)、セグメンテーション違反は発生しません...特に、ほとんどの単語に収まるため、短いアドレスを要求している場合はそうです長さ。これは本当に悪い考えです。起動時にコードを実行しない限りカーネルは保護されますが、ディスク上の何かを簡単に上書きする可能性があります。

なぜ-1のアドレスを印刷したのかわかりません。

于 2012-05-28T02:21:33.570 に答える