4

私はmallocとreallocを試していて、次の問題のコードを考え出しました。

制限を設定せずに、サイズが不明な文字列を作成したい。ユーザーに文字の数を要求することもできますが、ユーザーが各文字を入力するときにstrのサイズを変更します。

だから私はmalloc+reallocでこれをやろうとしていて、ユーザーが新しい文字を入力するたびに、reallocを使用して文字を保持するための+1個のメモリを要求するという考えでした。

これを実装しようとしているときに、私は間違いを犯し、次のことを行うことになりました。

int main () {
    /* this simulates the source of the chars... */
    /* in reality I would do getch or getchar in the while loop below... */

    char source[10];
    int i, j;
    for (i=0, j=65; i<10; i++, j++) { 
            source[i] = j;
    }

    /* relevant code starts here */

    char *str = malloc(2 * sizeof(char)); /* space for 1 char + '\0' */
    int current_size = 1;

    i = 0;
    while(i<10) {
            char temp = source[i];
            str[current_size-1] = temp;
            str[current_size] = '\0';
            current_size++;
            printf("new str = '%s' | len = %d\n", str, strlen(str));
            i++;
    }

    printf("\nstr final = %s\n", str);

    return 0;

} 

realloc部分はまだ実装されていないことに注意してください。

このコードをコンパイルして実行すると、次の出力が得られました

new str = 'A' | len = 1
new str = 'AB' | len = 2
new str = 'ABC' | len = 3
new str = 'ABCD' | len = 4
new str = 'ABCDE' | len = 5
new str = 'ABCDEF' | len = 6
new str = 'ABCDEFG' | len = 7
new str = 'ABCDEFGH' | len = 8
new str = 'ABCDEFGHI' | len = 9
new str = 'ABCDEFGHIJ' | len = 10

プログラムがクラッシュすると予想したため、これらの結果は奇妙でした。strには2文字のスペースがあり、コードはメモリを要求せずに2文字を超える文字をstrに追加しています。私の理解では、これは私が所有していないメモリに書き込んでいることを意味するため、ランタイムエラーが発生するはずです。

それで...なぜこれが機能するのですか?

(コンパイラーはGCC 4.3.4です。)

前もって感謝します。

編集: free()を呼び出すと、エラーが通知される可能性があることを示唆しているコメント投稿者の1人。上記のコードでfree()を呼び出してみましたが、コードを実行してもエラーは発生しませんでした。ただし、ソース配列にアイテムを追加し、freeを呼び出した後、次のエラーが発生しました。

*glibcが検出されました./prog:free():無効な次のサイズ(高速):0x09d67008 * *

4

5 に答える 5

7

割り当てられたメモリを超えて書き込んでいるため、コードの動作は未定義です。

コードがたまたま一度(または何度も)クラッシュしなかったという事実は、それを変更しません。

未定義の動作は、コードがクラッシュする必要があることを意味するものではありません。strあなたの場合、あなたが上書きしている直後に、たまたまメモリがあります。そのメモリを上書きした場合の実際の影響は不明です(他の変数の値を変更したり、ヒープを破壊したり、核攻撃を開始したりする可能性があります)。

于 2012-05-16T07:38:12.123 に答える
4

glibc-2.14からのように、メモリ割り当ては次のようにサイズとして割り当てられ、境界線を設定するため、2バイトサイズ "char * str = malloc(2 * sizeof(char))"を割り当てると、割り当てられたメモリは16バイト以上なので、項目を追加してプログラムエラーが発生する可能性があります。

struct _bucket_dir bucket_dir[] = {

    { 16,   (struct bucket_desc *) 0},

    { 32,   (struct bucket_desc *) 0},

    { 64,   (struct bucket_desc *) 0},

    { 128,  (struct bucket_desc *) 0},

    { 256,  (struct bucket_desc *) 0},

    { 512,  (struct bucket_desc *) 0},

    { 1024, (struct bucket_desc *) 0},

    { 2048, (struct bucket_desc *) 0},

    { 4096, (struct bucket_desc *) 0},

    { 0,    (struct bucket_desc *) 0}};   /* End of list marker */


void *malloc(unsigned int len)
{
    struct _bucket_dir  *bdir;
    struct bucket_desc  *bdesc;
    void            *retval;

    /*
     * First we search the bucket_dir to find the right bucket change
     * for this request.
     */
    for (bdir = bucket_dir; bdir->size; bdir++)
        if (bdir->size >= len)
            break;
    if (!bdir->size) {
        printk("malloc called with impossibly large argument (%d)\n",
            len);
        panic("malloc: bad arg");
    }
    /*
     * Now we search for a bucket descriptor which has free space
     */
    cli();  /* Avoid race conditions */
    for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next) 
        if (bdesc->freeptr)
            break;
    /*
     * If we didn't find a bucket with free space, then we'll 
     * allocate a new one.
     */
    if (!bdesc) {
        char        *cp;
        int     i;

        if (!free_bucket_desc)  
            init_bucket_desc();
        bdesc = free_bucket_desc;
        free_bucket_desc = bdesc->next;
        bdesc->refcnt = 0;
        bdesc->bucket_size = bdir->size;
        bdesc->page = bdesc->freeptr = (void *) cp = get_free_page();
        if (!cp)
            panic("Out of memory in kernel malloc()");
        /* Set up the chain of free objects */
        for (i=PAGE_SIZE/bdir->size; i > 1; i--) {
            *((char **) cp) = cp + bdir->size;
            cp += bdir->size;
        }
        *((char **) cp) = 0;
        bdesc->next = bdir->chain; /* OK, link it in! */
        bdir->chain = bdesc;
    }
    retval = (void *) bdesc->freeptr;
    bdesc->freeptr = *((void **) retval);
    bdesc->refcnt++;
    sti();  /* OK, we're safe again */
    return(retval);
}
于 2012-05-16T11:04:41.553 に答える
2

「クラッシュ」を含むが必須ではない未定義の動作をトリガーしているという事実に加えて、アプリケーションは実際に書き込み先のメモリを所有していると思います

最新のオペレーティングシステムでは、メモリはページ単位で処理されます。メモリのチャンクは2バイトよりはるかに大きくなります。AFAIK mallocは、OSに全ページを要求し、必要に応じてそれらを内部で分割します。(注:実装に依存しますが、少なくともglibcはこのように動作すると思います。)したがって、技術的にはあなたのものであるため、OSではメモリに書き込むことができます。内部的には、mallocは通常、ページを分割し、要求ごとにその一部を提供します。したがって、ヒープ上の別の変数を上書きする可能性があります。または、mallocの見解によれば、まだ要求されるのを待っているメモリに範囲を超えて書き込みます。

OSからまだ割り当てられていないページ、または読み取り専用としてマークされているページに書き込もうとした場合にのみ、クラッシュが発生することが予想されます。

于 2012-05-16T08:23:43.867 に答える
1

[そのような操作の動作が定義されていないという事実を置く]

ヒープの整合性は、reallocまたはfreeを呼び出すときにチェックされることが多く、書き込みのたびにチェックされるのではなく、クラッシュするほどオーバーライドしなかった可能性があります。

最後に無料で電話をかけなかったことに注意してください。そうすると、おそらくクラッシュするでしょう。

于 2012-05-16T07:39:21.743 に答える
0

前の答えに追加すると、実際には2のスペースがなく、メモリ内の単なるポインタです。どこかで、mallocは2文字のスペースを与えたことを覚えていますが、それはmallocの内部動作です。

次の小さな実験を試して、これがどのように機能するかを確認できます。

最初のポインタのすぐ後ろに別のポインタを作成します。

char *str2 = str + 5;

/* or you could simply malloc another */

char *str2 = malloc(2);

printf("str=%d, str2=%d\n",str,str2);

/* to eyeball the pointers actually received
and note the difference in the two pointers. 
You will need to raise source length to at least
that much to see the results below
*/

そして、最初のprintfの後にループに別のprintfを導入します。

printf("new str2 = '%s' | len = %d\n", str2, strlen(str2));

遅かれ早かれstr2も同じ文字を表示し始めます。

HTH

于 2012-05-16T07:57:00.103 に答える