web-dev-qa-db-ja.com

セグメンテーション違反を回避するためのC / C ++での配列サイズのチェック

したがって、Cがメモリにアクセスするときに配列の境界チェックを行わないことはよく知られています。現在、myArray[7]として初期化したときにint myArray[3]を呼び出すと、プログラムは保護されたメモリのおかげでsegfaultとクラッシュを引き起こします。

これで、myFunc(int *yourArray)などの関数に引数があり、配列に少なくとも8つのスロットが必要であることがわかっている場合、カスタムエラーをスローするために、事前にmyArray[7]が不正かどうかを確認できます。 :

"Sorry, yourArray is too small for this function. We need 8 ints of space."

のではなく

"Segmentation fault."
6
CJxD

バッファオーバーフロー未定義の動作 の例であるため、必要に応じて配列の境界をチェックすることは実装固有です(= this は、UBが本当に悪い理由を説明します)。

また、一般的には 決定不能な問題 でもあります。実際にプログラムを実行せずに、静的に発見( 静的プログラム分析 によって、たとえばC++ソースコードを)簡単に示すことができます。 )すべてのバッファオーバーフローは 停止問題 と同等です。 ライスの定理 についても読んでください。

ただし、いくつかの(部分的な)実用的なツールが存在します(特にLinuxの場合)。

  • コードにassertまたはstatic_assert- sを追加したり、ランタイムチェックを実行したりできます。

  • Frama-C の静的コードアナライザーを見つけて使用することができます(現在はCコードで機能します)。

  • [〜#〜] melt [〜#〜] を使用してGCCコンパイラをカスタマイズできます。

  • すべての警告とデバッグ情報を使用してコードをコンパイルする必要があります。 g++ -Wall -Wextra -g[〜#〜] gcc [〜#〜] を使用している場合。

  • 少なくともテストでは、プログラムを valgrind で実行する可能性があります。

  • address sanitizer を使用できます。コンパイルフラグに-fsanitize=addressを追加します(テスト時)。

  • 特にCでは(場合によってはC++でも)、配列ポインターとそのサイズの両方を渡す慣習は(たとえば snprintf(3 ) または strncmp(3) do)。 Cでは、structフレキシブル配列メンバー を使用して、フレキシブル配列のサイズをstruct内に格納することもできます

ところで、CおよびC++のポインタ演算機能により、バッファオーバーフローを見つけるのがさらに難しくなります。

C++ 11では、プレーンな配列と生のポインターを避け、 標準コンテナースマートポインター を使用することをお勧めします。

答えは非常に簡単です。安全性が必要な場合は、実際にそれを提供するものを使用してください。これはCではなく、生のCスタイルの配列でもありません。

Cおよびraw配列の基本的なスタイルからそれほど離れることなく、C++および_std::vector_を_[i]_を.at(i)に置き換えて使用し、境界チェックを取得できます。

代わりに_std::vector_を使用すると、配列に関するほとんどの問題が簡単になります。 .size()メンバー関数を使用して、ベクターの現在のサイズを確認できます。ほとんどの場合、何かを追加したいときはその.Push_back()メンバー関数を使用するだけなので、そうする必要はありません。

少なくとも理論的には、Cでほとんど同じようなことを行うことができますが、そうすることは比較的醜くなります。 (たとえば)ポインターと現在の割り当てサイズをstructに入れるラッパーを定義することはそれほど難しくありませんが、すべての操作を実行する関数を定義する必要があります。既存のコードがそれをどのように使用または処理するかを知らないという事実に耐えます。私はこれを数回行いましたが、それが十分に必要な場合は、それを機能させることができます。

4
Jerry Coffin

対応する配列の長さのポインタわからないを受け取る関数。明示的にパラメーターとして渡す必要があります。

void myFunc(int *yourArray, size_t yourArrayLen)

それが終わったら、エラーをスローするのは簡単です。

もちろん、これでも呼び出し側が間違った長さを与える可能性は残っています。次のいずれかがなければ、それを実際に防ぐことはできません。

  • 配列を格納するカスタムデータ型を実装し、カプセル化を使用して長さが常に実際の長さと同期していることを確認する、または
  • 静的配列のみを許可します。

    void myFunc(int (*yourArray)[8]);
    
4
Rufflewind

C(++)では、ポインタから最初の要素への配列の長さを取得する方法はありません。 (MSVCRTには _ msize のようなプラットフォーム固有の関数がありますが、それはonlymallocedポインターで機能します。 )

配列を関数に渡すときに通常行われることは、実行時に境界チェックを実行できるようにポインタとともに長さを渡すことです(== --- ==)

void myFunc(int* yourArray, int length)
{
    if (length < 8)
    {
        puts("Sorry, yourArray is too small for this function. We need 8 ints of space.");
        return;
    }

    // ...
}

void caller()
{
    int arr[LEN];
    myFunc(arr, LEN);
}
1
dan04

割り当てるブロックに関する追加情報を保持するmallocのカスタムラッパーを使用(または独自に作成)します。私が使用するものは、すべての割り当てにいくつかの「ガードバイト」を追加し、割り当ての長さをa [-1]として埋め込み、割り当て解除時にガードバイトおよびその他のものをチェックします。

0
ddyer