ここでの問題は、Cの文字列を別の言語(C ++やJavaなど)と同じように処理しようとしていることです。この言語では、任意の量のデータを簡単に追加または読み取ることができるサイズ変更可能なベクトルです。
C文字列ははるかに低いレベルです。それらは単に文字の配列(またはそのような配列へのポインター。配列はCの最初の要素へのポインターのように扱うことができます)であり、文字列は最初のnull文字までのその配列内のすべての文字として扱われます。これらの配列は固定サイズです。任意のサイズの文字列が必要な場合は、を使用して自分malloc()
で割り当てるか、希望するサイズでスタックに割り当てる必要があります。
ここで少し紛らわしいのは、非標準タイプを使用していることですstring
。コンテキストを考えると、それはあなたのから来ていると思いますcs50.h
、そしてそれは単なるtypedefchar *
です。;char *
の代わりに実際に使用すると、おそらく混乱が少なくなります。string
typedefを使用すると、実際に何が起こっているのかがわかりにくくなります。
最初の問題から始めましょう。
string word = strcat(argv[1], "\n");
strcat()
2番目の文字列を最初の文字列に追加します。これは、最初の文字列のnullターミネータから始まり、2番目の文字列のnullに達するまで、2番目の文字列の最初の文字に置き換えられます。これが機能するためには、最初の文字列を含むバッファに、2番目の文字列を収めるのに十分なスペースが必要です。そうでない場合は、他の任意のメモリを上書きする可能性があります。これにより、プログラムがクラッシュしたり、その他の予期しない動作が発生したりする可能性があります。
これがイラストです。argv[1]
に単語が含まれていて、hello
バッファにこれに必要なスペースが正確にあるとします。それが他のデータになった後。例として記入しましother
たが、実際にはそうではありませんが、何でもかまいません。重要な場合と重要でない場合があります。
+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \0| o | t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+
ここで、strcat()
を追加するために使用すると"\n"
、次のようになります。
+---+---+---+---+---+---+---+---+---+---+---+---+
| h | e | l | l | o | \n| \0| t | h | e | r | \0|
+---+---+---+---+---+---+---+---+---+---+---+---+
other
後のデータを上書きしたことがわかりますhello
。これはあらゆる種類の問題を引き起こす可能性があります。これを修正するには、を新しい文字列にコピーする必要があります。このargv[1]
文字列には、十分なスペースともう1つの文字があります(末尾のnullを忘れないでください)。を呼び出しstrlen()
て文字列の長さを取得し、に1を追加\n
し、末尾のnullに1を追加して、必要な長さを取得できます。
実際には、コマンドラインから入力した単語にaを追加するのではなく、入力単語からを削除するか、最後の文字()を除くすべてを比較するために使用する\n
ことをお勧めします。一般に、Cでは文字列の追加を避けるのが最善です。文字列を追加すると、メモリを割り当ててコピーする必要があり、間違いを犯しやすく、非効率になる可能性があるためです。高級言語は通常、詳細を処理し、文字列の追加を容易にしますが、それでも同様に非効率的です。\n
strncmp()
\n
編集後、これを次のように変更しました。
char* temp = argv[1];
char* word = strcat(temp, "\n");
ただし、これには同じ問題があります。Achar *
は文字配列へのポインタです。変数temp
は、実際の値ではなく、ポインタをコピーしているだけです。それはまだ同じバッファを指しています。これがイラストです。私はデモンストレーションの目的でアドレスを作成しています。実際のマシンでは、これらの間にさらに多くのオブジェクトがありますが、デモンストレーションの目的にはこれで十分です。
+------------+---------+-------+
| name | address | value |
+------------+---------+-------+
| argv | 1000 | 1004 |-------+
| argv[0] | 1004 | 1008 | --+ <-+
| argv[1] | 1006 | 1016 | --|---+
| argv[0][0] | 1008 | 'm' | <-+ |
| argv[0][1] | 1009 | 'y' | |
| argv[0][2] | 1010 | 'p' | |
| argv[0][3] | 1011 | 'r' | |
| argv[0][4] | 1012 | 'o' | |
| argv[0][5] | 1013 | 'g' | |
| argv[0][6] | 1014 | 0 | |
| argv[1][0] | 1016 | 'w' | <-+ <-+
| argv[1][1] | 1017 | 'o' | |
| argv[1][2] | 1018 | 'r' | |
| argv[1][3] | 1019 | 'd' | |
| argv[1][4] | 1020 | 0 | |
+------------+---------+-------+ |
temp
これで、変数を作成するときargv[1]
に、新しいものにコピーするだけchar *
です。
+------------+---------+-------+ |
| name | address | value | |
+------------+---------+-------+ |
| temp | 1024 | 1016 | --+
+------------+---------+-------+
argv[1]
ちなみに、1より大きいことを確認せずにアクセスしようとしないでくださいargc
。誰かが引数を渡さない場合、それargv[1]
自体はアクセスできません。
次の問題に移ります。
string c = "abc";
// ...
char* duh = fgets(c, 20, input);
ここでは、静的文字列を参照しています"abc"
。のように、文字通りソースに表示される文字列"abc"
は、プログラムのメモリの特別な読み取り専用部分に入ります。私が言ったことを覚えておいてください。string
これはただの言い方char *
です。つまりc
、実際には、メモリのこの読み取り専用セクションへの単なるポインタです。abc
また、テキストで指定した文字(4、文字列を終了するnull文字)を格納するのに十分なスペースしかありません。fgets()
最初の引数として、読み取っている文字列を格納する場所を取り、2番目の引数としてそれが持つスペースの量を取ります。したがって、最大20バイトを、4つのスペースしかない読み取り専用バッファーに読み込もうとしています。
たとえば、次のように使用して、スタックで読み取るためのスペースを割り当てる必要があります。
char c[20];
または動的に、を使用してmalloc()
:
char *c = malloc(20);