0

プログラムに入力された最後の 10 個のコマンドを取得しようとしています。私が抱えている問題は、コマンドラインから引数を取得して配列に格納することですが、実際の文字配列値ではなくポインターを格納しているため、配列に値を書き続けています。現在の文字配列 (コマンドを入力するたびに変化します) を取得し、その値を配列に格納する方法を知りたいです。ポインターの問題であることはわかっていますが、これを修正する方法がわかりません

私のコードは以下です。私の char 配列 history[] は、次の入力で次のようになります。

入力 a 履歴[0] = a 入力 b 履歴[0] = b 履歴[1] = b 入力 c 履歴[0] = c 履歴[1] = c 履歴[2] = c

#define MAX 256
#define CMD_MAX 10
#define HISTORY_MAX 10

int make_history(char *history[], int history_counter, char *input_line)
{
  if(history_counter < HISTORY_MAX)
  {
    history[history_counter] = input_line;
  }
  return 1;
}



int make_tokenlist(char *buf, char *tokens[])
{
   char input_line[MAX];
   char *line;
   int i,n;

   i = 0;
   line = buf;
   tokens[i] = strtok(line, " ");

   do
   {
    i++;
    line = NULL;
    tokens[i] = strtok(line, " ");
   }

   while(tokens[i] != NULL);

   return i;
 }

int main()
{
  char input_line[MAX], *tokens[CMD_MAX], *history[HISTORY_MAX];
  int i, n, history_counter;

  while(1)
  {
    printf("abc> ");

    if (gets(input_line) != NULL)
    {
     n= make_tokenlist(input_line, tokens);
    }
    else
    {
     printf("error?");
     return 1;
    }

    make_history(history,history_counter,input_line);

    if(strcmp(tokens[0],"history")==0)
    {
     for (i = 0; i < HISTORY_MAX; i++)
     {
       if(history[i]!=NULL)
       {
         printf("%d. %s \n", i, history[i]);
       }
     }
    } 

    for (i = 0; i < n; i++)
    {
     printf("extracted token is %s\n", tokens[i]);
    }

    history_counter++; //increment history counter
  }
}
4

1 に答える 1

3

問題は (あなたが理解していると思います) と同じ文字配列へのポインターを格納していることですinput_line。つまり、string insidehistoryと input_line は同じデータです。

解決策は、 のコピーを作成し、それを にinput_line保存することhistoryです。単純な実装 (これは機能しません) は次のとおりです。

int make_history(char *history[], int history_counter, char *input_line)
{
    if(history_counter < HISTORY_MAX)
    {
        char input_copy[MAX];
        strcpy(input_copy, input_line);
        history[history_counter] = input_copy;
    }
    return 1;
}

ここで、他のどこでも使用されていないinput_copy行のコピーをメモリに作成しています ( )。それはうまくいくはずですよね?問題は、そのような変数を宣言すると (静的割り当てと呼ばれます)、変数のデータがスタックに格納されることです。関数が戻ると、その関数によってスタックに置かれたすべてのデータが破棄されます。がmake_history()返さinput_copyれると、破棄され、 とhistory[history_counter]同じ配列をinput_copy指していたので、プログラムによって他の目的に使用される可能性があり、入力行のコピーが含まれないメモリのビットを指しています。

動的割り当てを使用して、宣言されているスコープ内にのみ存在する静的に割り当てられたメモリの問題を回避できます。ヒープmalloc()と呼ばれるものにメモリを割り当てます(C では、C++ では を使用します)。自分でメモリを解放するように指示しない限り、プログラムの実行中にヒープ内のデータが破壊されることはありません。例:new

int make_history(char *history[], int history_counter, char *input_line)
{
    if(history_counter < HISTORY_MAX)
    {
        char *input_copy = (char *)malloc(MAX * sizeof(char));
        strcpy(input_copy, input_line);
        history[history_counter] = input_copy;
    }
    return 1;
}

ヒープ内のデータ (によって割り当てられたmalloc()) は、make_history()リターン後、そのメモリを自分で解放するか、プログラムが終了するまで存続するため、これは機能します。私がどのように使用しているかに注意してくださいsizeof(char)これは、プリミティブ型 ( int、 など)はchar、コンパイルするプロセッサの種類、使用しているコンパイラなどによってサイズが異なる可能性があるためです。C 仕様 (すべてのリビジョンだと思います)、および他のすべてのデータ型のサイズは の倍数で指定されます。)sizeof(char) == 1char

しかし、次の場合はどうでしょう。

// history_counter == 0
make_history(history,history_counter,input_line);
// ...
// later, when history_counter == 0 again somehow
make_history(history, history_counter, another_input_line);

2 番目make_history()に行ったのは、ポインターをinput_lineコピーから another_input_line のコピーに変更したことだけです。最初の のメモリを解放することはなく、input_lineそれへのすべてのポインタが消えています。これはメモリ リークと呼ばれます (そして、それらがすべて単純であれば素晴らしいことです!)。プログラムでこれが可能な場合 (たとえば、history_counterループを 0 に戻した場合)、これを修正する必要があります。最初にそのメモリを割り当てた後は、それを再利用できますよね?

int make_history(char *history[], int history_counter, char *input_line)
{
    if(history_counter < HISTORY_MAX)
    {
        if(history[history_counter] == NULL)
        {
            history[history_counter] = (char *)malloc(MAX * sizeof(char));
        }
        strcpy(history[history_counter], input_line);
        history[history_counter] = input_copy;
    }
    return 1;
}

ここで、history[history_counter]以前に行のコピーを既に保存しているかどうかを確認しています。そうでない場合malloc()は、最初にメモリを保存します。いずれにせよ、その後、配列にコピーinput_lineします。history(コードには、これを破る別の問題があります。以下を参照してください。)

動的割り当てについて知っておくべきもう 1 つのことは、それが私たちの主題であるように思われるため、 を使用して割り当てたメモリを解放するにはmalloc()、 を使用することfree()です。C++ の場合、 を使用して割り当てられたメモリを解放するnewには、 を使用しますdelete


あなたの質問へのコメントで言及されているように、別の可能な解決策:すぐに静的に10*256*sizeof(char)バイトを割り当てることができます。main()これはより単純なソリューションですが、必要なときだけでなく、常にそのメモリを使い果たします。あなたの例は単純で、それほど多くのメモリを必要としませんが、より大きな/より複雑なプログラムでは、その違いが重要になる可能性があります.


ところで、質問以外のコードのいくつかの問題:

では初期化history_counterしないmain()ため、 を呼び出したときの初期値は未定義ですmake_history()。初期化されていない変数 (宣言されているが、値が割り当てられていない) はランダムとして扱う必要があります。実際には、値はプロセッサ/システム、コンパイラ、OS に依存し、依存する必要はありません。したがって、 の 2 行目は次のようにmain()なります。

int i, n, history_counter = 0;

同様に、次のように宣言historyしました。

char *history[HISTORY_MAX];

また、そのポインターの配列を値に初期化しませんNULL。後で確認します:

if(history[i] != NULL)

history[i]ただし、 のデフォルト値がNULLallであるという保証はありませんihistory最初に配列をすべてのNULL値に初期化する必要があります。free()上記の実際の回答の最後の例も、この初期化が行われていることに依存しており、割り当てられていないアドレスにアクセスしようとすると、悪いことが起こる可能性があります。

于 2013-01-26T03:28:40.530 に答える