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

CodeGuruで「strlenvssizeof」というタイトルのスレッドを読んでいました返信の1つは、char配列を初期化するのはとにかく[原文のまま]悪い習慣だと述べています文字列リテラルを使用します。」

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


元の質問は次のとおりです:

#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によって変更される前に文字列のサイズを計算するのと同じですが、長さは

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


ここに返信

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

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

コメント

  • 最初の行の” const “に注意してください。作者がcではなくc ++を想定したのでしょうか? c ++では、”悪い習慣”です。これは、リテラルはconstである必要があり、最近のc ++コンパイラーは警告(またはエラー)を出すためですconstリテラルを非const配列に割り当てる方法について。
  • @Andr é C ++では、文字列リテラルをconst配列として定義しています。これが、安全な処理方法であるためです。彼らと一緒に。そのCは doesn ‘ t が問題なので、安全なことを強制する社会的ルールがあります
  • @Caleth。私は、返信の作成者がc ++の観点から”悪い習慣”に近づいていると主張しようとしていました。
  • @Andr é C ++では isn iv id =であるため、’悪い習慣ではありません。 “6c384c7da2”>

練習、’ストレートアップタイプエラー。 Cではタイプエラーである必要がありますが、’ではないため、 ‘禁止”

回答

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

そのコメントの作成者がそれを正当化することは決してなく、私はそのステートメントが不可解であると感じています。

Cでは(そしてあなたはこれをCとしてタグ付けしました)、それは」 ■文字列値を使用してcharの配列を初期化する唯一の方法です(初期化は割り当てとは異なります)。どちらでも記述できます

char string[] = "october"; 

または

char string[8] = "october"; 

または

char string[MAX_MONTH_LENGTH] = "october"; 

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

あなたができないことは

char string[]; string = "october"; 

または

char string[8]; string = "october"; 

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

同じトークンでは、書き込むことはできません

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

宣言の外部で配列のコンテンツを割り当てるには、strcpy/strncpy(0で終了する文字列の場合)または(その他のタイプのアレイの場合):

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! 

コメント

  • strncpyが正しい答えになることはめったにありません
  • @KeithThompson:同意しません。完全を期すために追加しました’。
  • は悪い習慣です。 ‘オーバーフローしておらず、メンテナンス中に壊れていないことを確認するために、文字通り文字数を数える必要がありました…例:スペルミスをseprateからseparateに修正すると、サイズが更新されないと壊れます。
  • djechlinに同意します。与えられた理由のために悪い習慣です。 JohnBode ‘の回答は’ “悪い習慣

    アスペクト(質問の主要部分です!!)では、配列を初期化するためにできることとできないことを説明しています。

  • マイナー:ivとしてstrlen()から返されたid = “6c384c7da2”>

length “値には、MAX_MONTH_LENGTHは、char string[]に必要な最大サイズを保持するために見た目が間違っていることがよくあります。 IMO、MAX_MONTH_SIZEはここの方が良いでしょう。

回答

私が覚えている唯一の問題は、文字列リテラルを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"; 

コメント

  • まだ’があります’ something_else

でバッファオーバーランが発生するリスクがあります。 がsizeof(buf)より大きい。私は通常、最後の文字buf[sizeof(buf)-1] = 0を設定して保護します。または、bufがゼロで初期化されている場合は、をコピーの長さとして使用します。

  • strlcpyまたはstrcpy_s、さらには必要な場合。
  • 修正済み。残念ながら、最新のコンパイラを使用する余裕がない限り、これを簡単に移植できる方法はありません(strlcpyおよびsnprintfに直接アクセスすることはできません。 MSVCでは、少なくとも注文とstrcpy_sは* nixにはありません。
  • @MaciejPiechotka:ええと、Unixがマイクロソフトが後援する付録kを拒否したことに感謝します。
  • 回答

    主な理由は、char[]

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

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

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

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

    stringを別の関数に渡すと、別の問題が発生します。そのため、他の関数は配列に書き込むことができます。配列の長さを他の関数に伝える必要があるため、 オーバーランが発生しません。string

    ただし、スレッドは、stringがnullで終了していない場合にこれがどのように爆発するかを説明しています。

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

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


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

    コメント

    • sizeof stringバッファのサイズ(8バイト)を示します。 ‘メモリが心配な場合は、strlenの代わりにその式の結果を使用してください。同様に、strcpyを呼び出す前に、ターゲットバッファがソース文字列if (sizeof target > strlen(src)) { strcpy (target, src); }に対して十分に大きいかどうかを確認できます。はい、配列を関数に渡す必要がある場合は、’物理サイズも渡す必要があります:foo (array, sizeof array / sizeof *array);
    • @ JohnBode-ありがとう、そしてそれらは良い点です。あなたのコメントを私の答えに取り入れました。
    • より正確には、配列名stringへのほとんどの参照は、、配列の最初の要素を指します。これにより、配列の境界情報が失われます。関数呼び出しは、これが発生する多くのコンテキストの1つにすぎません。 char *ptr = string;は別のものです。 string[0]でさえこの例です。 []演算子は、配列に直接作用するのではなく、ポインターに作用します。推奨読書: comp.lang.c FAQ のセクション6。
    • 最後に、実際に質問を参照する回答です。

    回答

    どちらのスレッドも表示しないことの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標準では、配列のいずれかの部分が初期化されると、すべてが初期化されると主張しています。したがって、この場合は、自分で行う方がよいでしょう。それが、treussが得ていたものだったのではないかと思います。

    確かに

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

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

    char whopping_big[8192] = {0}; 

    または

    char whopping_big[8192] = ""; 

    ps Forボーナスポイントとして、次のことができます。

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

    配列がオーバーフローしそうな場合に、コンパイル時間をゼロ除算エラーでスローします。

    回答

    「悪い習慣」の考えは、次の形式から来ていると思います:

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

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

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

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

    または直接:

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

    (ただし、もちろんほとんどの場合コードはおそらく重要ではありません)

    コメント

    • ‘コピーを作成するだけです変更しようとすると、コンパイラはそれよりも賢くなると思います
    • char time_buf[] = "00:00";のように’バッファを変更しますか?文字列リテラルに初期化されたchar *は最初のバイトのアドレスに設定されるため、変更しようとすると未定義の動作になります。文字列リテラル’のストレージのメソッドは不明です(実装が定義されています)が、char[]のバイトを変更することは完全に合法です。初期化により、バイトはスタックに割り当てられた書き込み可能スペースにコピーされます。’ s “効率が悪いまたは”悪い習慣” char* vs char[]は誤解を招く恐れがあります。

    回答

    それほど長い時間はありませんが、初期化char []は避けてください。 「string」はconstchar *であり、char *に割り当てているためです。したがって、このchar []をデータを変更するメソッドに渡すと、興味深い動作が可能になります。

    賞賛に値するように、char []とchar *を少し混ぜましたが、少し異なるので良くありません。

    char配列にデータを割り当てることには何の問題もありませんが、この配列を使用する目的は「文字列」(char *)として使用することであるため、これを変更してはならないことを忘れがちです。配列。

    コメント

    • 不正解。初期化により、文字列リテラルの内容が配列にコピーされます。配列オブジェクトは t constそのように定義しない限り、(Cの文字列リテラルはconstではありませんが、文字列リテラルを変更しようとすると未定義の動作が発生します。)char *s = "literal";にはあなたが話している’のような行動。 ‘はconst char *s = "literal";
    • “そして一般的に” asdf “は定数なので、constとして宣言する必要があります。” -iv id = “811864f2cd”であるため、同じ理由でint n = 42;constが必要になります。 >

    は定数です。

  • ‘どのマシンを使用しているかは関係ありません’。言語標準は、cが変更可能であることを保証します。 ‘は、1 + 12と評価するものとまったく同じくらい強力な保証です。 上記でリンクしたプログラムEFGHの出力以外の処理を行う場合は、Cの実装が不適合であることを示しています。
  • @Dainus:MSVCコンパイラには、’文字列プーリング’という最適化機能があります。それらの使用が読み取り専用であることを保証できる場合は、同一の文字列を読み取り専用セグメントに入れます。最適化をオフにして、’通常の’の動作を確認します。参考までに、”編集して続行”では、このオプションをオンにする必要があります。詳細はこちら: msdn.microsoft.com/en-us/library/s0s0asdt.aspx
  • Dainiusは多くの場合それを示唆していると思いますエラーは、変数自体にconst char *constのマークを付けて、バイトまたはポインター自体の変更を防ぐ必要があるというエラーですが、多くの場合、プログラマーは一方または両方を変更可能のままにして、ランタイムコードを型付き定数のように見えるものを変更します(ただし、定数ではありません)。
  • コメントを残す

    メールアドレスが公開されることはありません。 * が付いている欄は必須項目です