char s []とchar * sの違いは何ですか?


506

Cでは、次のような宣言で文字列リテラルを使用できます。

char s[] = "hello";

またはこのように:

char *s = "hello";

違いは何ですか?コンパイル時と実行時の両方で、ストレージ期間に関して実際に何が起こるか知りたいです。



8
char * s = "hello"、ここでsは実行時に別の文字列を指すことができます実行時に別の値を割り当てることができる定数ポインターではないことを意味しますp = "Nishant"、s []ここでsは定数ポインターです。 ..それは別の文字列を再割り当てすることはできませんが、s [index]で別の文字値を割り当てることができます。
Nishant Kumar

回答:


541

ここでの違いは

char *s = "Hello world";

メモリ"Hello world"読み取り専用部分に配置され、そのメモリsへのポインタを作成すると、このメモリへの書き込み操作が無効になります。

実行中:

char s[] = "Hello world";

リテラル文字列を読み取り専用メモリに配置し、文字列をスタックに新しく割り当てられたメモリにコピーします。このようにして

s[0] = 'J';

法的。


22
リテラル文字列"Hello world"は、どちらの例でも「メモリの読み取り専用部分」にあります。配列の例はそこを指し、配列の例は文字を配列要素にコピーします。
pmg 2009年

28
pmg:2番目のケースでは、リテラル文字列は必ずしも単一の連続したオブジェクトとしてメモリに存在する必要はありません。これは単なるイニシャライザであり、コンパイラは、内部に埋め込まれた文字値を含む一連の「ロードイミディエイトバイト」命令を非常に適切に発行できます。それら。
カフェ

10
char配列の例では必ずしも文字列をスタックに配置する必要はありません。ファイルレベルで表示される場合は、代わりに何らかの初期化されたデータセグメントに含まれる可能性があります。
caf

9
char s = "xx"は読み取り専用メモリにある必要がないことを指摘したい(たとえば、一部の実装ではMMUがない)。n1362 c1xドラフトは、そのような配列を変更すると未定義の動作が発生すると単に述べています。しかし、+ 1とにかく、その振る舞いに依存するのはばかげたことです。
paxdiablo 2009年

3
char msg[] = "hello, world!"; 文字列のみを含むファイルをクリーンアップしてコンパイルすると、初期化されたデータセクションになってしまいます。char * const最終的に読み取り専用データセクションになると宣言された場合。gcc-4.5.3
gcbenison 2012

152

まず、関数の引数では、これらはまったく同じです。

void foo(char *x);
void foo(char x[]); // exactly the same in all respects

その他のコンテキストでchar *は、ポインタをchar []割り当て、配列を割り当てます。前者の場合、文字列はどこに行くのですか?コンパイラは、文字列リテラルを保持するために静的な匿名配列を密かに割り当てます。そう:

char *x = "Foo";
// is approximately equivalent to:
static const char __secret_anonymous_array[] = "Foo";
char *x = (char *) __secret_anonymous_array;

この無名配列の内容をこのポインタを介して変更してはならないことに注意してください。効果は未定義です(多くの場合、クラッシュを意味します)。

x[1] = 'O'; // BAD. DON'T DO THIS.

配列構文を使用すると、新しいメモリに直接割り当てられます。したがって、変更は安全です。

char x[] = "Foo";
x[1] = 'O'; // No problem.

ただし、配列はそのスコープの範囲内でのみ存続するため、関数でこれを実行する場合は、この配列へのポインターを返したりリークしたりしないでくださいstrdup()。代わりに、またはと同様にコピーを作成してください。もちろん、配列がグローバルスコープで割り当てられていれば問題ありません。


72

この宣言:

char s[] = "hello";

値で初期化された1つのオブジェクトchar(サイズ6の配列)を作成sします'h', 'e', 'l', 'l', 'o', '\0'。この配列がメモリ内で割り当てられる場所、およびその配列の存続期間は、宣言が表示される場所によって異なります。宣言が関数内にある場合、宣言されたブロックの終わりまで存続し、ほぼ確実にスタックに割り当てられます。それは関数の外だ場合、それがされますおそらく、プログラムの実行時に書き込み可能なメモリに実行ファイルからロードされた「初期化データセグメント」内に格納します。

一方、この宣言は:

char *s ="hello";

2つのオブジェクトを作成します。

  • 読み取り専用 6つの配列char値を含む複数の'h', 'e', 'l', 'l', 'o', '\0'名前がないとされ、静的記憶域期間を(それはプログラムの全体の生命のために住んでいることを意味します)。そして
  • s名前が付けられていない読み取り専用配列の最初の文字の位置で初期化される、と呼ばれるポインタへの型の変数。

名前のない読み取り専用配列は通常、プログラムの「テキスト」セグメントにあります。これは、コード自体とともにディスクから読み取り専用メモリに読み込まれることを意味します。sメモリ内のポインタ変数の場所は、宣言がどこにあるかによって異なります(最初の例と同様)。


1
両方の "hello"の宣言では、メモリは十分な時間に割り当てられます。そして、別のことchar * p = "hello"ここで "hello"は、回答で述べたようにテキストセグメントに格納されます... char s []について= "hello"は、テキストセグメントパーツにも最初に格納され、実行時にリッカードが回答で述べたようにスタックにコピーされます。この点を明確にしてください。
Nishant Kumar

2
@Nishant:このchar s[] = "hello"場合、これ"hello"は、配列を初期化する方法をコンパイラに伝える初期化子です。テキストセグメントに対応する文字列が含まれる場合と含まれない場合sがあります。たとえば、静的な保存期間がある場合、の唯一のインスタンスは"hello"初期化されたデータセグメント(オブジェクトs自体)にある可能性があります。s自動保存期間がある場合でも、コピーではなくリテラルストアのシーケンスによって初期化できます(例:)movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
-ca

より正確には、GCC 4.8はそれを.rodataに入れ、リンカースクリプトはと同じセグメントにダンプします.text私の答えをください。
Ciro Santilli郝海东冠状病六四事件法轮功

@cafリッカードの最初の回答でchar s[] = "Hello world";は、リテラル文字列を読み取り専用メモリに入れ、その文字列をスタック上の新しく割り当てられたメモリにコピーするように書かれています。しかし、あなたの答えは、読み取り専用メモリに配置されたリテラル文字列についてのみ話し、文章の2番目の部分をスキップしますcopies the string to newly allocated memory on the stack。では、2番目の部分を指定しないと、答えが不完全になりますか?
KPMG

1
@AjaySinghNegi:他のコメントで述べたように(この回答とリッカードの回答に対して)、inの文字列char s[] = "Hellow world";は初期化子にすぎず、必ずしも個別の読み取り専用コピーとして保存されるわけではありません。s静的ストレージ期間がある場合、文字列の唯一のコピーはの場所の読み取り/書き込みセグメントにある可能性が高く、そうでsない場合でも、コンパイラーはコピーではなく、ロード即時命令などで配列を初期化することを選択できます。読み取り専用文字列から。重要なのは、この場合、初期化文字列自体にはランタイムが存在しないということです。
カフェ

60

宣言を考える

char *s0 = "hello world";
char s1[] = "hello world";

次の仮想メモリマップを想定します。

                    0x01 0x02 0x03 0x04
        0x00008000: 'h' 'e' 'l' 'l'
        0x00008004: 'o' '' 'w' 'o'
        0x00008008: 'r' 'l' 'd' 0x00
        ...
s0:0x00010000:0x00 0x00 0x80 0x00
s1:0x00010004: 'h' 'e' 'l' 'l'
        0x00010008: 'o' '' 'w' 'o'
        0x0001000C: 'r' 'l' 'd' 0x00

文字列リテラル"hello world"charconst charC ++で)の12要素の配列で、静的ストレージ期間を持ちます。つまり、そのメモリはプログラムの起動時に割り当てられ、プログラムが終了するまで割り当てられたままになります。文字列リテラルの内容を変更しようとすると、未定義の動作が発生します。

この線

char *s0 = "hello world";

自動ストレージ期間(変数が宣言されているスコープにのみ存在する変数を意味する)s0へのポインターとして定義し、文字列リテラル(この例では)のアドレスchar変数にs0コピーします0x00008000。以来という注意s0文字列リテラルを指し、それはそれを修正しようとする任意の関数(例えば、引数として使用すべきではないstrtok()strcat()strcpy()、など)。

この線

char s1[] = "hello world";

s112要素の配列として定義されchar(長さは文字列リテラルから取得されます)、自動保存期間があり、リテラルの内容を配列にコピーします。メモリマップからわかるように、文字列のコピーが2つあります"hello world"。違いは、に含まれる文字列を変更できることですs1

s0そしてs1、ほとんどの文脈で交換可能です。ここに例外があります:

sizeof s0 == sizeof (char*)
sizeof s1 == 12

type of &s0 == char **
type of &s1 == char (*)[12] // pointer to a 12-element array of char

s0別の文字列リテラルまたは別の変数を指すように変数を再割り当てできます。s1別の配列を指すように変数を再割り当てすることはできません。


2
仮説的なメモリマップは理解しやすいと思います!
midnightBlue

32

C99 N1256ドラフト

文字列リテラルには2つの異なる使用法があります。

  1. 初期化char[]

    char c[] = "abc";      

    これは「より魔法」であり、6.7.8 / 14「初期化」で説明されています。

    文字型の配列は、文字列リテラルで初期化できます。オプションで中括弧で囲むこともできます。文字列リテラルの連続する文字(スペースがある場合、または配列のサイズが不明である場合の終了のnull文字を含む)は、配列の要素を初期化します。

    したがって、これは次のショートカットです。

    char c[] = {'a', 'b', 'c', '\0'};

    他の通常の配列と同様に、c変更できます。

  2. 他の場所:それは以下を生成します:

    だからあなたが書くとき:

    char *c = "abc";

    これは次のようになります。

    /* __unnamed is magic because modifying it gives UB. */
    static char __unnamed[] = "abc";
    char *c = __unnamed;

    以下からの暗黙のキャストに注意してくださいchar[]char *常に合法です、。

    次にを変更するとc[0]__unnamedUBであるも変更されます。

    これは6.4.5「文字列リテラル」に記載されています。

    5変換フェーズ7では、文字列リテラルから生じる各マルチバイト文字シーケンスに、値0のバイトまたはコードが追加されます。次に、マルチバイト文字シーケンスを使用して、シーケンスを含めるのに十分な静的ストレージ期間と長さの配列を初期化します。文字列リテラルの場合、配列要素のタイプはcharであり、マルチバイト文字シーケンスの個々のバイトで初期化されます[...]

    6これらの配列が異なるかどうかは、それらの要素が適切な値を持っているかどうかは不明です。プログラムがそのような配列を変更しようとした場合の動作は未定義です。

6.7.8 / 32「初期化」は直接的な例を示します:

例8:宣言

char s[] = "abc", t[3] = "abc";

「プレーン」なchar配列オブジェクトsを定義し、tその要素は文字列リテラルで初期化されます。

この宣言は

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

配列の内容は変更可能です。一方、宣言

char *p = "abc";

pタイプ "pointer to char"で定義し、長さが4のタイプ "charの配列"で、要素が文字列リテラルで初期化されるオブジェクトを指すように初期化します。を使用pして配列の内容を変更しようとした場合の動作は未定義です。

GCC 4.8 x86-64 ELF実装

プログラム:

#include <stdio.h>

int main(void) {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

コンパイルして逆コンパイルします。

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

出力に含まれるもの:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

結論:GCCはchar*それをで.rodataはなくセクションに格納し.textます。

しかしなお、デフォルトのリンカスクリプトプット.rodata.text同じでセグメント実行持って、ない書き込み許可。これは次のようにして観察できます:

readelf -l a.out

を含む:

 Section to Segment mapping:
  Segment Sections...
   02     .text .rodata

同じことをする場合char[]

 char s[] = "abc";

私達は手に入れました:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

そのため、スタックに格納されます(と比較して%rbp)。


15
char s[] = "hello";

初期化子(5 + 1 秒)を保持するのに十分な長さのs配列でcharあることを宣言しchar、指定された文字列リテラルのメンバーを配列にコピーして配列を初期化します。

char *s = "hello";

s1つまたは複数(この場合は複数)へのポインタであることを宣言しchar、リテラルを含む固定(読み取り専用)位置に直接ポイントし"hello"ます。


1
sが変更されない場合、f(const char s [])またはf(const char * s)のいずれの方法が関数での使用に適していますか?
psihodelia

1
@psihodelia:関数宣言では違いはありません。どちらの場合sもへのポインタconst charです。
CBベイリー

4
char s[] = "Hello world";

ここでは、s私たちが希望する場合に上書きすることができます文字の配列は、あります。

char *s = "hello";

文字列リテラルは、このポインターsが指すメモリ内のどこかにこれらの文字ブロックを作成するために使用されます。ここで、オブジェクトを変更することで、それが指しているオブジェクトを再度割り当てることができますが、文字列リテラルを指している限り、それが指している文字のブロックは変更できません。


@bo Persson 2番目のケースで文字のブロックを変更できないのはなぜですか?
Pankaj Mahato 2014年

3

さらに、読み取り専用の目的では両方の使用が同じであるため、[]または*(<var> + <index>) 形式でインデックス付けすることにより、charにアクセスできることを考慮してください。

printf("%c", x[1]);     //Prints r

そして:

printf("%c", *(x + 1)); //Prints r

明らかに、あなたがやろうとした場合

*(x + 1) = 'a';

読み取り専用メモリにアクセスしようとしているため、おそらくセグメンテーション違反が発生します。


これはx[1] = 'a';segfaultと同じです(もちろんプラットフォームによって異なります)。
glglgl 2016

3

追加するだけです:サイズの値も異なります。

printf("sizeof s[] = %zu\n", sizeof(s));  //6
printf("sizeof *s  = %zu\n", sizeof(s));  //4 or 8

上記のように、配列の'\0'場合は最後の要素として割り当てられます。


2
char *str = "Hello";

上記のstrは、プログラムのバイナリイメージにハードコードされているリテラル値「Hello」を指すように設定されています。これは、メモリ内で読み取り専用としてフラグが立てられています。つまり、この文字列リテラルの変更はすべて違法であり、セグメンテーション違反がスローされます。

char str[] = "Hello";

文字列をスタック上の新しく割り当てられたメモリにコピーします。したがって、それに変更を加えることは許可され、合法です。

means str[0] = 'M';

strを "Mello"に変更します。

詳細については、同様の質問をご覧ください。

「char s []」ではなく「char * s」で初期化された文字列に書き込むときに、セグメンテーション違反が発生するのはなぜですか?


0

の場合:

char *x = "fred";

xは左辺値です -代入できます。しかし、次の場合:

char x[] = "fred";

xは左辺値ではなく、右辺値です-代入することはできません。


3
技術的にxは、は変更できない左辺値です。ただし、ほとんどすべてのコンテキストで、最初の要素へのポインタに評価され、その値は右辺値です。
caf

0
char *s1 = "Hello world"; // Points to fixed character string which is not allowed to modify
char s2[] = "Hello world"; // As good as fixed array of characters in string so allowed to modify

// s1[0] = 'J'; // Illegal
s2[0] = 'J'; // Legal

-1

ここでのコメントに照らして、次のことは明らかです。char * s = "hello"; 悪い考えであり、非常に狭い範囲で使用する必要があります。

これは、「constの正確さ」が「良いこと」であることを指摘する良い機会かもしれません。いつでも、どこでも、「const」キーワードを使用して、「リラックスした」呼び出し元またはプログラマーからコードを保護します。

十分なメロドラマ、ここで「const」でポインターを装飾するときに達成できることを示します。(注:ポインター宣言を右から左に読む必要があります。)ポインターを操作するときに自分を保護する3つの方法があります。

const DBJ* p means "p points to a DBJ that is const" 

—つまり、DBJオブジェクトはpを介して変更できません。

DBJ* const p means "p is a const pointer to a DBJ" 

つまり、pを介してDBJオブジェクトを変更できますが、ポインタp自体を変更することはできません。

const DBJ* const p means "p is a const pointer to a const DBJ" 

つまり、ポインタp自体を変更したり、pを介してDBJオブジェクトを変更したりすることはできません。

試行された定数の変更に関連するエラーは、コンパイル時にキャッチされます。constには実行時のスペースや速度のペナルティはありません。

(もちろんC ++コンパイラを使用していると思いますか?)

--DBJ


これはすべて正しいですが、質問とは関係ありません。そして、C ++コンパイラについてのあなたの仮定に関する限り、質問はC ++ではなくCとしてタグ付けされます。
ファビオはモニカ

char * s = "const string"に悪いことは何もありません。
ポールスミス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.