6 に答える
char
文字列リテラルの場合、コンパイラは実際には要素用に余分な要素を予約してい\0
ます。
// Create a new char array
char* str2 = (char*) malloc( strlen(str1) );
これは、新しい C プログラマーがよく犯す間違いです。にストレージを割り当てるときchar*
は、文字数 + 1 をさらに\0
. ここで余分なストレージを割り当てないということは、この行も不正であることを意味します
// Null-terminate the second one
str2[strlen(str1)] = '\0';
ここでは、割り当てたメモリの末尾を超えて実際に書き込みを行っています。X要素を割り当てるとき、アクセスできる最後の正当なバイトは、メモリアドレスオフセットX - 1
です。要素に書き込むと、X
未定義の動作が発生します。多くの場合は機能しますが、時限爆弾です。
正しい書き方は以下の通り
size_t size = strlen(str1) + sizeof(char);
char* str2 = (char*) malloc(size);
strncpy( str2, str1, size);
// Output the second one
cout << "Str2: " << str2 << endl;
この例では、str2[size - 1] = '\0'
は実際には必要ありません。このstrncpy
関数は、すべての余分なスペースをヌル ターミネータで埋めます。ここにはsize - 1
要素しかないstr1
ため、配列の最後の要素は不要であり、で埋められます\0
実際には、12番目の文字が「\ 0」である11ではなく長さ12の配列を割り当てていますか?
はい。
しかし、12バイト目なので
str2[11]
、割り当てられたメモリ空間の外側に書き込んでいることを意味するように書き込んでいませんが、割り当てられたのは11バイトだけですか?str2
str2[11]
はい。
malloc( strlen(str1) + 1 )
代わりに使用する方が良いでしょうmalloc( strlen(str1) )
か?
はい、2番目の形式は文字列をコピーするのに十分な長さではないためです。
このコードを実行しても、コンパイラの警告や実行時エラーは発生しないようです。
最も単純な場合を除いて、これを検出することは非常に難しい問題です。したがって、コンパイラの作成者は単に気にしません。
std::string
この種の複雑さは、C ++を記述している場合、生のCスタイルの文字列ではなく使用する必要がある理由です。これと同じくらい簡単です:
std::string str1 = "hello world";
std::string str2 = str1;
の戻り値に混乱していると思いますstrlen
。文字列の長さを返します。文字列を保持する配列のサイズと混同しないでください。この例を考えてみましょう:
char* str = "Hello\0 world";
文字列の途中にヌル文字を追加しましたが、これは完全に有効です。ここでは、配列の長さは 13 (12 文字 + 最後のヌル文字) ですがstrlen(str)
、最初のヌル文字の前に 5 文字あるため、5 が返されます。strlen
ヌル文字が見つかるまで文字を数えるだけです。
だから私はあなたのコードを使用する場合:
char* str1 = "Hello\0 world";
char* str2 = (char*) malloc(strlen(str1)); // strlen(str1) will return 5
strncpy(str2, str1, strlen(str1));
cout << "Str2: " << str2 << endl;
str2 配列の長さは 5 で、ヌル文字で終了することstrlen
はありません (カウントしないため)。これはあなたが期待したものですか?
リテラル"hello world"
はchar
次のような配列です。
{ 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' }
そうです、リテラルchar
のサイズは 12 秒です。
また、NUL ターミネータを含まない文字列の長さを返すmalloc( strlen(str1) )
ため、必要よりも 1 バイト少ないメモリを割り当てています。strlen
に書き込むとstr[strlen(str1)]
は、割り当てたメモリ量を 1 バイト超えて書き込むことです。
コンパイラはそれを教えてくれませんが、valgrindまたはシステムで利用可能な同様のプログラムを介してプログラムを実行すると、アクセスしてはならないメモリにアクセスしているかどうかがわかります。
標準 C 文字列の場合、文字列を格納する配列の長さは、文字列の長さよりも常に 1 文字長くなります。したがって、"hello world"
文字列の長さは 11 ですが、12 エントリのバッキング配列が必要です。
この理由は単純に、文字列の読み取り方法にあります。これらの文字列を処理する関数は、基本的に文字列の文字を 1 つずつ読み取り、終了文字を見つけて'\0'
この時点で停止します。この文字が欠落している場合、これらの関数は、ホスト オペレーティング システムがアプリケーションを強制終了する原因となる保護されたメモリ領域にヒットするか、終了文字が見つかるまで、メモリの読み取りを続けます。
また、長さ 11 の文字配列を初期化して文字列を書き込むと、"hello world"
大きな問題が発生します。配列は少なくとも 12 文字を保持すると予想されるためです。これは、メモリ内の配列に続くバイトが上書きされることを意味します。予期せぬ副作用を引き起こします。
また、C++ を使用している場合は、std:string
. このクラスは、C++ を使用している場合にアクセスでき、文字列の処理が向上します。それを調べる価値があるかもしれません。