問題は (あなたが理解していると思います) と同じ文字配列へのポインターを格納していることです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) == 1
char
しかし、次の場合はどうでしょう。
// 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]
ただし、 のデフォルト値がNULL
allであるという保証はありませんi
。history
最初に配列をすべてのNULL
値に初期化する必要があります。free()
上記の実際の回答の最後の例も、この初期化が行われていることに依存しており、割り当てられていないアドレスにアクセスしようとすると、悪いことが起こる可能性があります。