1

私は C でポインターと構造体を使用する方法を学んでいます。当然のことながら、言語がどのように機能するかをさらに理解するために、意図的にコードを壊そうとしています。期待どおりに動作するテストコードを次に示します。

#include <stdio.h>
#include <stdlib.h>

struct pair {
    int x;
    int y;
};

typedef struct pair pair;

void p_struct( pair ); //prototype

int main( int argc, char** argv ) {
    pair *s_pair;
    int size, i;

    printf( "Enter the number of pair to make: " );
    scanf( "%d", &size );
    getchar();
    printf( "\n" );

    s_pair = (pair*)malloc( size * sizeof(pair) );

    for( i = 0; i < size; i++ ) {
        s_pair[i].x = i;
        s_pair[i].y = i;
        p_struct( s_pair[i] );
    }

    getchar();

    return (EXIT_SUCCESS);
}

void p_struct( pair s_pair ) {
    printf( "\n%d %d\n", s_pair.x, s_pair.y );
}

前に述べたように、このコードは私が知る限り機能しています。

次に、コードの一部を次のように変更することにしました。

for( i = 0; i < size + 3; i++ ) {
    s_pair[i].x = i;
    s_pair[i].y = i;
    p_struct( s_pair[i] );
}

この変更により、期待したセグ フォールト エラーは発生しませんでした。scanf 関数を使用して変数サイズに値を割り当てるときに明示的に設定したバッファーを超えたにもかかわらず、すべての「ペア」が出力されました。

ポインターを理解しているので (間違っている場合は訂正してください)、サイズsize*sizeof(pair)のメモリの連続ブロックは、ペアs_pair型のポインターに対して malloc 関数を呼び出したときに、ヒープ内のメモリ マネージャーによって予約されます。私がしたことは、 for ループを条件i < size + 3に変更したときに、メモリの最後に割り当てられたアドレスを超えたことです。

これを正しく理解していれば、ポインターが予約済みのメモリ制限を超えて、その右側に隣接するものが他のデータによって占有されていないために、たまたまクリアになったのでしょうか? バッファをオーバーフローさせたときのこれは正常な動作ですか?

さらに、 i < size + 15の for ループ条件でテストしたときに、セグ フォールトを受け取りました。問題は、それでも出力を印刷することです。のように、私が作成したp_struct関数に従って、サイズ= 10の場合、ペア「0 0」からペア「24 24」を出力します。プログラムは、最後にある getchar() の 1 つに到達した後でのみ、seg fault によってクラッシュします。私のプログラムは、バッファを超えるペアに値を割り当て、それらを画面に出力し、getchar() に到達したときに突然セグメント障害でクラッシュすることを決定できますか? i < size + 3に問題はないようです(まだ間違っていますが)。

念のため、通常のポインター配列を使用してこの動作もテストしました。

int size, i, *ptr;

scanf( "%d", &size );

ptr = (int*)malloc( size * sizeof(int) );

for( i = 0; i < size + 15; i++ )
    ptr[i] = i;

これにより、上記とまったく同じ結果が得られます。i < サイズ + 3では、セグ フォールトの問題はないようです。

最後に、配列でもテストしました。

int i, array[10];

for( i = 0; i < 25; i++ )
    array[i] = i;

i < 25の条件では、必ず seg fault が発生します。i < 15に変更すると、seg fault は発生しません。

私の記憶が正しければ、ポインターの配列と配列の唯一の違いは、配列に割り当てられたメモリがヒープではなくスタックにあることです (これについてはよくわかりません)。それを念頭に置いて、array[10]がセグ フォールトを生成しない場合にi < 15という事実を考慮すると、なぜi < 25が問題になるのでしょうか? その for ループの間、配列はスタックの一番上にありませんか? 余分な 60 バイトを気にしないのに、なぜ余分な 100 バイトを気にするのでしょうか? その配列バッファの上限が、スタック全体用に予約されているメモリの任意のチャンクの最後までないのはなぜですか?

うまくいけば、これらすべてが、少し酔っ払った男のとりとめのないものを読むことを決心した人にとって意味のあるものになることを願っています.

4

4 に答える 4

2

Cの輝かしい世界へようこそ!

メモリ割り当て関数 ( malloccallocreallocなど) は、ヒープ上にあるメモリを提供します。それらの 1 つを呼び出したときに、プログラムに十分なスペースがない場合、システム コールを実行してスペースを増やします。ただし、これは正確な増分では行われません (多くの場合、いくつかのページ全体の増分で行われます)。配列の末尾を超えて (または配列の先頭よりも前に) インデックスを作成している場合でも、プログラムの正当なアドレス空間の範囲内にいます。プログラムが所有するセグメントを離れた場合にのみ、Segmentation Violationが発生します。

Valgrindを使用してプログラムを検査することを強くお勧めします。特に、何かを破壊することによって意図的にメモリについて学習しようとしている場合はなおさらです。とりわけ、割り当ての両側にカナリア値を保存して、境界外にアクセスしている場合を把握し、二重解放とメモリ リークについて警告します。

于 2013-09-02T04:55:05.843 に答える
1

malloc を呼び出すと、メモリが共通ブロック サイズの倍数で割り当てられるため、必要以上のメモリが割り当てられる場合があります。ブロック サイズが 64 バイトで、10 バイトのみを要求した場合、OS は 64 バイトを提供するため、プログラムが観察している動作である要求された範囲を超えてメモリにアクセスできます。

于 2013-09-02T10:59:57.373 に答える