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


44

私は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");

最初の行の「定数」に注意してください。著者がcではなくc ++を想定しているのでしょうか?C ++では、リテラルはconstである必要があり、最近のc ++コンパイラはconstリテラルを非const配列に割り当てることについて警告(またはエラー)を出すため、「悪い習慣」です。
アンドレ

@AndréC ++は、文字列リテラルをconst配列として定義します。これが、それを処理する唯一の安全な方法だからです。C 問題ではないので、安全なことを強制する社会的ルールがあります。
カレス

@キャレス。私は、返信の作者がC ++の観点から「悪い習慣」に近づいていると主張しようとしていました。
アンドレ

@AndréはC ++で悪い習慣ではありません。それは習慣はないからです。まっすぐな型エラーです。それはあるべき Cにおける型エラーが、それはあなたを伝えるスタイルガイドのルールを持っている必要がありますので、「それは禁じられています」、ではありません
Caleth

回答:


59

とにかく、文字列リテラルで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番目の2つの場合、配列のサイズは宣言の一部として指定されます(8およびMAX_MONTH_LENGTH、それが何であれ)。

あなたがすることはできませんやっていることのような書き込みものです

char string[];
string = "october";

または

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

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

同じトークンで、あなたは書くことができません

char string[] = foo;

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

編集

配列を初期化して、文字列を保持するように配列スタイルの初期化子を使用することもできます。たとえば、

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で終わる文字列の場合)または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!


@KeithThompson:異論ではなく、完全を期すために追加しただけです。
ジョンボード

16
それchar[8] str = "october";は悪い習慣であることに注意してください。私は、文字通りのchar必ずそれがオーバーフローしなかった作るために自分自身をカウントしなければならなかったし、それからスペル誤りを訂正する...例えばメンテナンス中で壊れるseprateまでseparateサイズが更新されていない場合は解除されます。
ジェクリン

1
私はdjechlinに同意します、それは与えられた理由から悪い習慣です。JohnBodeの答えは、「悪い練習」の側面(質問の主要部分です!!)についてはまったくコメントしていません。配列を初期化するためにできることまたはできないことを説明しています。
マストフ

マイナー:「として長さ」の値が返されたからstrlen()使って、ヌル文字が含まれていないMAX_MONTH_LENGTHために必要な最大サイズを保持するためにchar string[]しばしば見え間違ってIMO、。MAX_MONTH_SIZEここで良いだろう。
chux -復活モニカ

10

私が思い出す唯一の問題は、文字列リテラルを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)では、読み取り専用としてマークされたページに書き込もうとするとクラッシュします。他のプラットフォームでは、「September」などを印刷する場合があります。

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

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';

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

strlcpyまたは使用する必要があるstrcpy_s場合でもsnprintf
user253751

一定。あなたは、最新のコンパイラでの作業の贅沢を持っている(しない限り、残念ながらこれを行うための簡単なポータブルな方法はありませんstrlcpyし、snprintfMSVCに直接アクセスすることはできません、少なくとも命令でとstrcpy_s* nixの上ではありません)。
マチェイピエチョトカ

@MaciejPiechotka:まあ、Unixはマイクロソフトが後援した別館kを拒否しました。
デュプリケータ

6

どちらのスレッドも表示しないことの1つは次のとおりです。

char whopping_great[8192] = "foo";

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"));

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


5

char[]に、プログラム内で簡単に使用できる変数/構造体のサイズがないためです。

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

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

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

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

string別の関数に渡して別の関数が配列に書き込むことができる場合、別の課題が生じます。オーバーランが発生しないよう、他の関数に配列の長さを伝える必要があります。stringの結果を渡すこともできますstrlen()が、スレッドstringは、ヌルで終了していない場合にこれがどのように爆発するかを説明します。

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

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


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


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

1
@JohnBode-ありがとう、それは良い点です。あなたのコメントを回答に盛り込みました。

1
より正確には、ほとんどの配列名への参照は、配列の最初の要素を指すへのstring暗黙的な変換をもたらしchar*ます。これにより、配列の境界情報が失われます。関数呼び出しは、これが発生する多くのコンテキストの1つにすぎません。char *ptr = string;別です。string[0]この例でさえあります。[]オペレータは、アレイ上のポインタではなく、直接に取り組んでいます。推奨読書:comp.lang.c FAQのセクション6 。
キーストンプソン

最後に、実際に質問を参照する回答!
マストフ

2

「悪い練習」という考えは、このフォームが

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

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

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

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

または直接:

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

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


変更しようとした場合にのみコピーを作成しませんか?コンパイラはそれよりも賢いと思います
コールジョンソン

1
char time_buf[] = "00:00";バッファを変更する場所のような場合はどうですか?char *バイトを修正しながら、文字列リテラルの保管の方法は、(実装が定義された)不明であるため、それを修正しようとすると、未定義の動作につながるように、文字列リテラルで初期化は、最初のバイトのアドレスに設定されてchar[]完全に合法であるために初期化は、スタックに割り当てられた書き込み可能なスペースにバイトをコピーします。ニュアンスを詳しく説明せずに「効率が悪い」または「悪い習慣」であるchar* vs char[]と言うのは誤解を招きます。
ブレーデンベスト

-3

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

ほのめかしたように、char []とchar *を少し混ぜたのは、少し違うので良くありません。

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


3
間違っています。初期化により、文字列リテラルの内容が配列にコピーされます。配列オブジェクトはconst、そのように定義しない限りそうではありません。(Cの文字列リテラルはそうconstではありませんが、文字列リテラルを変更しようとしても未定義の動作があります。)char *s = "literal";話しているような動作があります。それがより良いと書かれていますconst char *s = "literal";
キース・トンプソン

確かに私のせいで、char []とchar *を混ぜました。しかし、コンテンツを配列にコピーすることについてはあまり確信がありません。MS Cコンパイラでのクイックチェックは、「char c [] = "asdf";」であることを示しています。constセグメントに「string」を作成し、このアドレスを配列変数に割り当てます。それが実際に非const char配列への代入を避けることについて言った理由です。
ダイニウス

私は懐疑的です。このプログラムを試してどのような出力が得られるか教えてください。
キーストンプソン

2
「そして一般的に「asdf」は定数なので、constとして宣言する必要があります。」-同じ理由は、定数であるため、conston int n = 42;を必要42とします。
キーストンプソン

1
使用しているマシンは関係ありません。言語標準cは、変更可能であることを保証します。これは、と1 + 1評価されるものとまったく同じくらい強力な保証2です。場合は、私は上記にリンクされているプログラムは、印刷以外の何かを行いEFGH、それが非準拠のC実装を示します。
キーストンプソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.