web-dev-qa-db-ja.com

文字列リテラルでchar []を初期化することは悪い習慣ですか?

私は、CodeGuruで "strlen vs sizeof"というタイトルのスレッドを読んでいました 、そして 返信の1つ は、「とにかくcharを初期化することは、[sic]悪い習慣です。文字列リテラルの配列。」

これは本当ですか、それとも彼の(「エリートメンバー」ではありますが)意見だけですか?


これが元の質問です:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

正しい。サイズは長さプラス1にする必要がありますか?

これは出力です

the size of september is 8 and the length is 9

サイズはきっと10本になるはずです。それはstrcpyによって変更される前にsizeof文字列を計算するようなものですが、その後の長さです。

構文に何か問題がありますか?


これが 返信 です。

とにかく、文字列リテラルでchar配列を初期化することは悪い習慣です。したがって、常に次のいずれかを実行します。

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");
46
Cole Johnson

とにかく、文字列リテラルでchar配列を初期化することは悪い習慣です。

そのコメントの作者は、それを正当化することは決してありません。

Cでは(これにCのタグを付けました)、これがinitialize文字列値を持つcharの配列を初期化する唯一の方法です(初期化は代入とは異なります)。あなたはどちらかを書くことができます

_char string[] = "october";
_

または

_char string[8] = "october";
_

または

_char string[MAX_MONTH_LENGTH] = "october";
_

最初のケースでは、配列のサイズは初期化子のサイズから取得されます。文字列リテラルは、0バイトで終了するcharの配列として格納されるため、配列のサイズは8( 'o'、 'c'、 't'、 'o'、 'b'、 'e'、 'r')です。 、0)。 2番目の2つのケースでは、配列のサイズは宣言の一部として指定されます(8と_MAX_MONTH_LENGTH_、何であれ)。

あなたができないことは、次のようなものを書くことです

_char string[];
string = "october";
_

または

_char string[8];
string = "october";
_

最初のケースでは、stringの宣言はincompleteです。これは、配列サイズが指定されておらず、サイズを取得する初期化子がないためです。どちらの場合でも、_=_は機能しません。これは、a)stringなどの配列式が割り当てのターゲットになっていない可能性があり、b)_=_演算子が内容をコピーするように定義されていないためです。とにかく、アレイを別のものに。

同じ理由で、あなたは書くことができません

_char string[] = foo;
_

ここで、foocharの別の配列です。この形式の初期化は、文字列リテラルでのみ機能します。

[〜#〜]編集[〜#〜]

これを修正して、次のように配列スタイルの初期化子で文字列を保持するために配列を初期化することもできると言います

_char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};
_

または

_char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII
_

ただし、文字列リテラルを使用する方が目には簡単です。

編集2

宣言の外にある配列のcontentsを割り当てるには、_strcpy/strncpy_(0で終了する文字列の場合)またはmemcpy(その他の場合)を使用する必要があります。配列のタイプ):

_if (sizeof string > strlen("october"))
  strcpy(string, "october");
_

または

_strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!
_
62
John Bode

私が覚えている唯一の問題は、文字列リテラルをchar *に割り当てることです。

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

たとえば、次のプログラムを見てください。

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

これは私のプラットフォーム(Linux)で、読み取り専用としてマークされたページに書き込もうとするとクラッシュします。他のプラットフォームでは、「9月」などと表示される場合があります。

つまり、リテラルによる初期化では特定の量の予約が行われるため、これは機能しません。

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

しかし、これは

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

最後の発言として-私はstrcpyをまったく使用しません:

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

一部のコンパイラはそれを安全な呼び出しに変更できますが、strncpyの方がはるかに安全です。

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';
11

主に、プログラム内で簡単に使用できる変数/構文に_char[]_のサイズが含まれないためです。

リンクからのコードサンプル:

_ char string[] = "october";
 strcpy(string, "september");
_

stringは、7文字または8文字としてスタックに割り当てられます。この方法でnullで終了しているかどうかは思い出せません。リンク先のスレッドは、終了していると述べています。

その文字列の上に「9月」をコピーすると、明らかにメモリがオーバーランします。

stringを別の関数に渡して、他の関数が配列に書き込むことができるようにすると、別の課題が生じます。 itでオーバーランが発生しないように、配列の長さを他の関数に通知する必要があります。 stringstrlen()の結果と一緒に渡すこともできますが、stringがnullで終了していない場合に、スレッドがこれを爆発させる方法について説明します。

固定サイズ(できれば定数として定義)の文字列を割り当ててから、配列と固定サイズを他の関数に渡す方がよいでしょう。 @John Bodeのコメントは正しく、これらのリスクを軽減する方法があります。また、それらを使用するには、ユーザー側でより多くの努力が必要です。

私の経験では、_char[]_を初期化した値は通常、そこに配置する必要がある他の値に対して小さすぎます。定義された定数を使用すると、その問題を回避できます。


_sizeof string_は、バッファーのサイズ(8バイト)を示します。メモリが気になる場合は、strlenの代わりにその式の結果を使用してください。
同様に、strcpyを呼び出す前に、ターゲットバッファがソース文字列if (sizeof target > strlen(src)) { strcpy (target, src); }に対して十分な大きさであるかどうかを確認できます。
はい、配列を関数に渡す必要がある場合は、その物理サイズも渡す必要があります:foo (array, sizeof array / sizeof *array);。 – John Bode

6
user53019

どちらのスレッドも取り上げないことの1つは、これです。

char whopping_great[8192] = "foo";

vs.

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

前者は次のようなことをします:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

後者はmemcpyのみを実行します。 C標準では、配列のいずれかの部分が初期化されると、すべて初期化されると主張しています。したがって、この場合は自分で行うことをお勧めします。私はそれがトレウスが得ていたものだったのではないかと思います。

確かに

char whopping_big[8192];
whopping_big[0] = 0;

どちらよりも優れています:

char whopping_big[8192] = {0};

または

char whopping_big[8192] = "";

pSボーナスポイントについては、次のことができます。

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

配列をオーバーフローさせようとしている場合に、コンパイル時間ゼロ除算エラーをスローします。

6
Richard Fife

「悪い習慣」のアイデアは、このフォームが

char string[] = "october is a Nice month";

ソースマシンコードからスタックへ暗黙的にstrcpyを作成します。

その文字列へのリンクのみを処理する方が効率的です。のように:

char *string = "october is a Nice month";

または直接:

strcpy(output, "october is a Nice month");

(もちろん、ほとんどのコードではおそらく問題ではありません)

2
toto