0

テキストファイルを最初に1行ずつ、次にトークンに解析するためのCコードがあります。

これは、1行ずつ解析する関数です。

int parseFile(char *filename) {
//Open file
FILE *file = fopen(filename, "r");
//Line, max is 200 chars
int pos = 0;
while (!feof(file)) {
    char *line = (char*) malloc(200*sizeof(char));
    //Get line
    line = fgets(line, 200, file);
    line = removeNewLine(line);
    //Parse line into instruction
    Instruction *instr = malloc(sizeof(instr));
    instr = parseInstruction(line, instr);
    //Print for clarification
    printf("%i: Instr is %s arg1 is %s arg2 is %s\n", 
        pos,
        instr->instr,
        instr->arg1,
        instr->arg2);
    //Add to end of instruction list
    addInstruction(instr, pos);
    pos++;
    //Free line
    free(line);
}
return 0;

}

そして、これは各行をいくつかのトークンに解析し、最終的にそれを命令構造体に入れる関数です。

Instruction *parseInstruction(char line[], Instruction *instr) {
//Parse instruction and 2 arguments
char *tok = (char*) malloc(sizeof(tok));
tok = strtok(line, " ");
printf("Line at %i tok at %i\n", (int) line, (int) tok);
instr->instr = tok;
tok = strtok(NULL, " ");
if (tok) {
    instr->arg1 = tok;
    tok = strtok(NULL, " ");
    if(tok) {
        instr->arg2 = tok;
    }
}
return instr;

}

printf("Line at %i tok at %i\n", (int) line, (int) tok);ParseInstructionの行は、常に同じ2つの値を出力しますが、これらのポインターアドレスが変更されないのはなぜですか。parseInstructionが毎回一意のポインター値を返すことを確認しましたが、各命令のinstrスロットには同じポインターがあります。

わかりやすくするために、命令は次のように定義されています。

typedef struct Instruction {

char *instr;
char *arg1;
char *arg2;

} 命令;

私は何が間違っているのですか?

4

4 に答える 4

4

これがstrtok動作方法です。実際に操作対象の文字列を変更し、区切り文字をその文字列に置き換えて、'\0'その文字列にポインタを返します。(マニュアルページの「バグ」セクションをstrtok(3)参照してください。これは実際にはバグではありませんが、人々が通常は期待しない動作にすぎません。)したがって、イニシャルtokは常にの最初の文字を指しますline

ちなみに、これは:

char *tok = (char*) malloc(sizeof(tok));
tok = strtok(line, " ");

最初tokにの戻り値を指すように設定しmalloc、次に、の戻り値を指すように再割り当てします。これにより、の戻り値strtokを完全に破棄しますmalloc。これを書くのと同じです:

int i = some_function();
i = some_other_function();

some_function();の戻り値を完全に破棄します。ただし、の戻り値を破棄するとメモリリークが発生mallocするため、さらに悪化します。

于 2012-12-03T03:00:37.157 に答える
2

コードで確認できる問題がいくつかあります。最もひどいものはにありparseInstructionます:

char *tok = (char*) malloc(sizeof(tok));
tok = strtok(line, " ");

ここでは、にメモリを割り当てますtokが、次にtok、の結果に等しく設定しstrtokます。これにより、割り当てられたメモリが事実上到達不能になります(使用されることはありません)。これはメモリリークであるだけでなく、最初のトークンへのポインタを返すtok == lineため、常にtrueになることを意味します(最初のパラメータ!=で呼び出されると、通常は文字列の先頭になります)。strtokNULL

これが最初の問題を説明しています(値が同じであるということです)。繰り返しの繰り返しで値が常に同じであるという問題に関して、エドマンドの答えはそれをうまくまとめています。完全に偶然に、あなたは同じメモリのチャンクを解放し、mallocしているのです。

char *tok = malloc(...)行を削除strtokし、意図したとおりに文字列を操作できるようにする必要があります。それはあなたのメモリリークを修正します。それがどのように機能するかという理由で、あなたはまだあなたのプリントアウトに同じ値を見るでしょうstrtok

ただし、このコードで最終的に遭遇する重大な問題の1つは、のメモリ空間をline指すポインタを使用して命令構造体に割り当てていることです。これはループの各反復中に正常に機能しますが、各反復の終わりに解放すると、そのメモリはなくなり、他の誰かによって再利用されるだけです。今は偶然にうまくいくかもしれませんが、最終的にはいたるところにSEGFAULTが発生します。それぞれstruct Instructionに独自のプライベートメモリを持たせたい場合は、次のようなことを行う必要があります。

// You already do this part, but with a bug; use this code instead
Instruction *instr = (Instruction *)malloc(sizeof(Instruction));

// Then, inside parseInstruction, make these relevant changes
instr->instr = (char *)malloc(strlen(tok) + 1);
strcpy(instr->instr, tok);

instr->arg1 = (char *)malloc(strlen(tok) + 1);
strcpy(instr->arg1, tok);

instr->arg2 = (char *)malloc(strlen(tok) + 1);
strcpy(instr->arg2, tok);

ここでの要点はmalloc、構造体の各フィールドにサイズの新しいメモリチャンクstrlen(...) + 1(「+1」はNULバイトを表す)を作成し、その新しいメモリに文字列をコピーすることです。

解放するときは、必ずすべてのメモリを解放してください。

// free each field
free(instr->instr);
free(instr->arg1);
free(instr->arg2);

// free the struct itself
free(instr);

コードをクリーンアップするために実行できることは他にもいくつかあります(たとえば、多くの割り当ては不要です。fgets(line, 200, file)と同じ効果がありますline = fgets(line, 200, file))が、これで正しい方向に進むはずです。

于 2012-12-03T03:24:25.847 に答える
1

してはいけませsizeof(char)sizeof(tok)

修正する

char *tok = (char*) malloc(sizeof(tok));
    to

char *tok = (char*) malloc(sizeof(char));
于 2012-12-03T02:59:29.617 に答える
1

行にメモリを割り当て、処理してから解放します。(他のメモリを命令に割り当てて解放しませんが、それは別です。)

何が起こっているのかというと、行からメモリを解放すると、200文字のサイズの穴があります(さらに簿記スペースもあります)。使用しているユーザーはこのmalloc穴を見つけて、次回ループで再利用します。

これらはすべて個別の割り当てですが、メモリ内の同じスペースがそれぞれに再利用されるのはまさにそのためです。もちろん、これは偶然として扱う必要があります。他の割り当てを変更すると、変更される可能性があります。

于 2012-12-03T03:06:18.983 に答える