18

char *を使用して割り当てられた で動作する文字列置換関数を C で記述しようとしましたmalloc()。開始文字列の文字ではなく、文字列を検索して置換するという点で少し異なります。

十分なスペースが割り当てられているため、検索文字列と置換文字列が同じ長さ (または置換文字列が検索文字列より短い) の場合は簡単です。を使用しようとするとrealloc()、ダブル フリーを実行していることを示すエラーが表示されますrealloc()

おそらく、ちょっとしたコードが役に立ちます:

void strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while (find = strstr(find, search)) {

        if (delta > 0) {
            realloc(input, strlen(input) + delta);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) - (find - input));
        memmove(find, replace, replaceLen);
    }
}

プログラムrealloc()は、置換された文字列が最初の文字列よりも長くなるインスタンスで試行するまで機能します。(それはまだ動作しますが、エラーと結果を吐き出すだけです)。

それが役立つ場合、呼び出しコードは次のようになります。

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

void strrep(char *input, char *search, char *replace);

int main(void) {
    char *input = malloc(81);

    while ((fgets(input, 81, stdin)) != NULL) {
        strrep(input, "Noel", "Christmas");
    }
}
4

8 に答える 8

14

原則として、ユーザー提供のバッファに対して解放または再割り当てを行うべきではありません。ユーザーが領域を割り当てた場所 (モジュール内、別の DLL) がわからないため、ユーザー バッファーで割り当て関数を使用することはできません。

関数内で再割り当てを行うことができない場合は、置換を 1 回だけ行うなど、動作を少し変更する必要があります。これにより、ユーザーは結果の文字列の最大長を計算し、これに十分な長さのバッファーを提供できるようになります。交換が発生します。

次に、複数の置換を行う別の関数を作成できますが、結果の文字列にスペース全体を割り当て、ユーザー入力文字列をコピーする必要があります。次に、割り当てた文字列を削除する方法を提供する必要があります。

その結果:

void  strrep(char *input, char *search, char *replace);
char* strrepm(char *input, char *search, char *replace);
void  strrepmfree(char *input);
于 2008-08-04T11:19:54.447 に答える
12

まず、パーティーに遅れてすみません。これは私の最初のスタックオーバーフローの答えです。:)

指摘されているように、realloc() が呼び出されると、再割り当てされるメモリへのポインタを変更できる可能性があります。この場合、引数「string」は無効になります。再割り当てしても、関数が終了すると変更は範囲外になります。

OP に答えるために、 realloc() は新しく再割り当てされたメモリへのポインタを返します。戻り値はどこかに保存する必要があります。通常、次のようにします。

data *foo = malloc(SIZE * sizeof(data));
data *bar = realloc(foo, NEWSIZE * sizeof(data));

/* Test bar for safety before blowing away foo */
if (bar != NULL)
{
   foo = bar;
   bar = NULL;
}
else
{
   fprintf(stderr, "Crap. Memory error.\n");
   free(foo);
   exit(-1);
}

TyBoer が指摘しているように、この関数への入力として渡されるポインターの値を変更することはできません。好きなように割り当てることができますが、変更は関数の最後で範囲外になります。次のブロックでは、関数が完了すると、「入力」は無効なポインターになる場合とそうでない場合があります。

void foobar(char *input, int newlength)
{
   /* Here, I ignore my own advice to save space. Check your return values! */
   input = realloc(input, newlength * sizeof(char));
}

マークは、関数の出力として新しいポインターを返すことで、これを回避しようとします。これを行うと、入力に使用したポインターを二度と使用しないという責任が呼び出し元にあります。戻り値と一致する場合、同じ場所への 2 つのポインターがあり、そのうちの 1 つで free() を呼び出すだけで済みます。それらが一致しない場合、入力ポインターは、プロセスが所有している場合と所有していない場合があるメモリを指すようになりました。逆参照すると、セグメンテーション違反が発生する可能性があります。

次のように、入力にダブル ポインターを使用できます。

void foobar(char **input, int newlength)
{
   *input = realloc(*input, newlength * sizeof(char));
}

呼び出し元がどこかに入力ポインターの複製を持っている場合、その複製は今でも無効である可能性があります。

ここでの最もクリーンな解決策は、関数呼び出し元の入力を変更しようとするときに realloc() を使用しないことだと思います。新しいバッファを malloc() して返し、古いテキストを解放するかどうかを呼び出し元に決定させるだけです。これには、呼び出し元が元の文字列を保持できるという追加の利点があります!

于 2008-08-08T21:37:38.887 に答える
6

まだ試していないので、暗闇の中でのショットですが、再割り当てすると、malloc と同じようにポインターが返されます。realloc は必要に応じてポインターを移動できるため、次のことを行わないと、無効なポインターを操作する可能性が高くなります。

input = realloc(input, strlen(input) + delta);
于 2008-08-04T11:14:48.827 に答える
6

他の誰かがパーティーに遅れたことを謝罪しました - 2ヶ月半前. まあ、私はかなりの時間をソフトウェア考古学に費やしています。

元の設計でのメモリ リークやオフ バイ ワン エラーについて誰も明示的にコメントしていないことに興味があります。そして、二重解放エラーが発生している理由を正確に教えてくれるメモリリークを観察していました(正確には、同じメモリを複数回解放しているためです-そして、すでに解放されたメモリを踏みにじった後にそうしています)。

分析を行う前に、私はあなたのインターフェイスが優れているとは言えないと言う人々に同意します。ただし、メモリ リーク/踏みつぶしの問題に対処し、「メモリを割り当てる必要がある」という要件を文書化した場合は、「OK」である可能性があります。

問題は何ですか?さて、realloc() にバッファを渡すと、realloc() は使用する領域への新しいポインタを返しますが、その戻り値は無視します。その結果、 realloc() はおそらく元のメモリを解放し、次に同じポインターを再度渡すと、元の値を再度渡すため、同じメモリを2回解放していると不平を言います。これはメモリ リークを引き起こすだけでなく、元のスペースを使い続けていることを意味します。John Downey の闇のショットは、あなたが realloc() を誤用していることを指摘していますが、それがどれほど深刻であるかは強調していません。文字列を終了する NUL '\0' に十分なスペースを割り当てていないため、off-by-one エラーもあります。

メモリ リークが発生するのは、呼び出し元に文字列の最後の値を伝えるメカニズムを提供していないためです。元の文字列とその後のスペースを踏みにじり続けたため、コードは機能しているように見えますが、呼び出し元のコードがスペースを解放すると、ダブルフリー エラーが発生するか、コア ダンプまたは同等のエラーが発生する可能性があります。メモリ制御情報は完全にスクランブルされます。

あなたのコードはまた、無限の成長を防ぎません。'Noel' を 'Joyeux Noel' に置き換えることを検討してください。毎回、7文字追加しますが、置き換えられたテキストに別のノエルを見つけて、それを拡張する、など. 私の修正 (以下) はこの問題に対処していません。おそらく、単純な解決策は、検索文字列が置換文字列に含まれているかどうかを確認することです。別の方法として、置換文字列をスキップして、その後の検索を続行することもできます。2 つ目は、対処すべき重要なコーディングの問題がいくつかあります。

したがって、呼び出された関数の私の提案するリビジョンは次のとおりです。

char *strrep(char *input, char *search, char *replace) {
    int searchLen = strlen(search);
    int replaceLen = strlen(replace);
    int delta = replaceLen - searchLen;
    char *find = input;

    while ((find = strstr(find, search)) != 0) {
        if (delta > 0) {
            input = realloc(input, strlen(input) + delta + 1);
            find = strstr(input, search);            
        }

        memmove(find + replaceLen, find + searchLen, strlen(input) + 1 - (find - input));
        memmove(find, replace, replaceLen);
    }

    return(input);
}

このコードはメモリ割り当てエラーを検出しません。realloc() が失敗すると、おそらくクラッシュします (ただし、そうでない場合はメモリ リークが発生します)。メモリ管理の問題について詳しくは、Steve Maguire の「Writing Solid Code」という本を参照してください。

于 2008-10-21T02:41:12.413 に答える
4

コードを編集して、html エスケープ コードを削除してください。

C/C++ を使用してからしばらく経ちましたが、元のブロックの後にメモリに空きがある場合にのみ、成長する realloc はメモリ ポインタ値を再利用します。

たとえば、次のように考えてください。

(xxxxxxxxxxx........)

ポインターが最初の x を指している場合、および . は空きメモリの場所を意味し、変数が指すメモリ サイズを 5 バイト増やすと、成功します。これはもちろん単純化された例ですが、ブロックは整列のために特定のサイズに切り上げられます。

ただし、その後さらに 10 バイト拡大しようとして、使用できるのが 5 バイトしかない場合は、メモリ内のブロックを移動してポインターを更新する必要があります。

ただし、あなたの例では、変数へのポインターではなく、文字へのポインターを関数に渡しているため、 strrep 関数は使用中の変数を内部的に調整できるかもしれませんが、それは strrep 関数へのローカル変数であり、呼び出し元のコードには、元のポインター変数の値が残ります。

ただし、このポインター値は解放されています。

あなたの場合、入力が原因です。

しかし、私は別の提案をします。あなたの場合、入力変数は実際に入力されているように見えます。そうであれば、まったく変更しないでください。

したがって、このような副作用を追跡するのは難しいため、inputを変更せずに、やりたいことを行う別の方法を見つけようとします。

于 2008-08-04T11:17:32.330 に答える
3

これはうまくいくようです。

char *strrep(char *string, const char *search, const char *replace) {
    char *p = strstr(string, search);

    if (p) {
        int occurrence = p - string;
        int stringlength = strlen(string);
        int searchlength = strlen(search);
        int replacelength = strlen(replace);

        if (replacelength > searchlength) {
            string = (char *) realloc(string, strlen(string) 
                + replacelength - searchlength + 1);
        }

        if (replacelength != searchlength) {
            memmove(string + occurrence + replacelength, 
                        string + occurrence + searchlength, 
                        stringlength - occurrence - searchlength + 1);
        }

        strncpy(string + occurrence, replace, replacelength);
    }

    return string;
}

ため息、それを吸うことなくコードを投稿する方法はありますか?

于 2008-08-04T11:39:47.853 に答える
3

realloc は奇妙で複雑で、大量のメモリを 1 秒間に何度も処理する場合にのみ使用する必要があります。つまり、実際にコードを高速化する場所です。

私はコードを見てきました

realloc(bytes, smallerSize);

が使用され、バッファのサイズを変更して小さくしました。約 100 万回動作した後、何らかの理由で realloc は、バッファーを短縮していても、新しいコピーが適切に作成されると判断しました。したがって、悪いことが起こってから 1/2 秒後にランダムな場所でクラッシュします。

常に realloc の戻り値を使用してください。

于 2011-05-16T22:57:53.513 に答える
0

私の簡単なヒント。

代わりに:
void strrep(char *input, char *search, char *replace)
試してください:
void strrep(char *&input, char *search, char *replace)

そして体内よりも:
input = realloc(input, strlen(input) + delta);

一般に、関数の引数を値/参照および realloc() の説明として渡すことについて読んでください:)。

于 2008-08-04T11:20:58.657 に答える