Cでは、次のような宣言で文字列リテラルを使用できます。
char s[] = "hello";
またはこのように:
char *s = "hello";
違いは何ですか?コンパイル時と実行時の両方で、ストレージ期間に関して実際に何が起こるか知りたいです。
Cでは、次のような宣言で文字列リテラルを使用できます。
char s[] = "hello";
またはこのように:
char *s = "hello";
違いは何ですか?コンパイル時と実行時の両方で、ストレージ期間に関して実際に何が起こるか知りたいです。
回答:
ここでの違いは
char *s = "Hello world";
はメモリ"Hello world"
の読み取り専用部分に配置され、そのメモリs
へのポインタを作成すると、このメモリへの書き込み操作が無効になります。
実行中:
char s[] = "Hello world";
リテラル文字列を読み取り専用メモリに配置し、文字列をスタックに新しく割り当てられたメモリにコピーします。このようにして
s[0] = 'J';
法的。
"Hello world"
は、どちらの例でも「メモリの読み取り専用部分」にあります。配列の例はそこを指し、配列の例は文字を配列要素にコピーします。
char msg[] = "hello, world!";
文字列のみを含むファイルをクリーンアップしてコンパイルすると、初期化されたデータセクションになってしまいます。char * const
最終的に読み取り専用データセクションになると宣言された場合。gcc-4.5.3
まず、関数の引数では、これらはまったく同じです。
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()
。代わりに、またはと同様にコピーを作成してください。もちろん、配列がグローバルスコープで割り当てられていれば問題ありません。
この宣言:
char s[] = "hello";
値で初期化された1つのオブジェクトchar
(サイズ6の配列)を作成s
します'h', 'e', 'l', 'l', 'o', '\0'
。この配列がメモリ内で割り当てられる場所、およびその配列の存続期間は、宣言が表示される場所によって異なります。宣言が関数内にある場合、宣言されたブロックの終わりまで存続し、ほぼ確実にスタックに割り当てられます。それは関数の外だ場合、それがされますおそらく、プログラムの実行時に書き込み可能なメモリに実行ファイルからロードされた「初期化データセグメント」内に格納します。
一方、この宣言は:
char *s ="hello";
2つのオブジェクトを作成します。
char
値を含む複数の'h', 'e', 'l', 'l', 'o', '\0'
名前がないとされ、静的記憶域期間を(それはプログラムの全体の生命のために住んでいることを意味します)。そしてs
名前が付けられていない読み取り専用配列の最初の文字の位置で初期化される、と呼ばれるポインタへの型の変数。名前のない読み取り専用配列は通常、プログラムの「テキスト」セグメントにあります。これは、コード自体とともにディスクから読み取り専用メモリに読み込まれることを意味します。s
メモリ内のポインタ変数の場所は、宣言がどこにあるかによって異なります(最初の例と同様)。
char s[] = "hello"
場合、これ"hello"
は、配列を初期化する方法をコンパイラに伝える初期化子です。テキストセグメントに対応する文字列が含まれる場合と含まれない場合s
があります。たとえば、静的な保存期間がある場合、の唯一のインスタンスは"hello"
初期化されたデータセグメント(オブジェクトs
自体)にある可能性があります。s
自動保存期間がある場合でも、コピーではなくリテラルストアのシーケンスによって初期化できます(例:)movl $1819043176, -6(%ebp); movw $111, -2(%ebp)
。
char s[] = "Hello world";
は、リテラル文字列を読み取り専用メモリに入れ、その文字列をスタック上の新しく割り当てられたメモリにコピーするように書かれています。しかし、あなたの答えは、読み取り専用メモリに配置されたリテラル文字列についてのみ話し、文章の2番目の部分をスキップしますcopies the string to newly allocated memory on the stack
。では、2番目の部分を指定しないと、答えが不完全になりますか?
char s[] = "Hellow world";
は初期化子にすぎず、必ずしも個別の読み取り専用コピーとして保存されるわけではありません。s
静的ストレージ期間がある場合、文字列の唯一のコピーはの場所の読み取り/書き込みセグメントにある可能性が高く、そうでs
ない場合でも、コンパイラーはコピーではなく、ロード即時命令などで配列を初期化することを選択できます。読み取り専用文字列から。重要なのは、この場合、初期化文字列自体にはランタイムが存在しないということです。
宣言を考える
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"
はchar
(const char
C ++で)の12要素の配列で、静的ストレージ期間を持ちます。つまり、そのメモリはプログラムの起動時に割り当てられ、プログラムが終了するまで割り当てられたままになります。文字列リテラルの内容を変更しようとすると、未定義の動作が発生します。
この線
char *s0 = "hello world";
自動ストレージ期間(変数が宣言されているスコープにのみ存在する変数を意味する)s0
へのポインターとして定義し、文字列リテラル(この例では)のアドレスをchar
変数にs0
コピーします0x00008000
。以来という注意s0
文字列リテラルを指し、それはそれを修正しようとする任意の関数(例えば、引数として使用すべきではないstrtok()
、strcat()
、strcpy()
、など)。
この線
char s1[] = "hello world";
s1
12要素の配列として定義され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
別の配列を指すように変数を再割り当てすることはできません。
C99 N1256ドラフト
文字列リテラルには2つの異なる使用法があります。
初期化char[]
:
char c[] = "abc";
これは「より魔法」であり、6.7.8 / 14「初期化」で説明されています。
文字型の配列は、文字列リテラルで初期化できます。オプションで中括弧で囲むこともできます。文字列リテラルの連続する文字(スペースがある場合、または配列のサイズが不明である場合の終了のnull文字を含む)は、配列の要素を初期化します。
したがって、これは次のショートカットです。
char c[] = {'a', 'b', 'c', '\0'};
他の通常の配列と同様に、c
変更できます。
他の場所:それは以下を生成します:
だからあなたが書くとき:
char *c = "abc";
これは次のようになります。
/* __unnamed is magic because modifying it gives UB. */
static char __unnamed[] = "abc";
char *c = __unnamed;
以下からの暗黙のキャストに注意してくださいchar[]
にchar *
常に合法です、。
次にを変更するとc[0]
、__unnamed
UBであるも変更されます。
これは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
)。
char s[] = "hello";
初期化子(5 + 1 秒)を保持するのに十分な長さのs
配列でchar
あることを宣言しchar
、指定された文字列リテラルのメンバーを配列にコピーして配列を初期化します。
char *s = "hello";
s
1つまたは複数(この場合は複数)へのポインタであることを宣言しchar
、リテラルを含む固定(読み取り専用)位置に直接ポイントし"hello"
ます。
s
もへのポインタconst char
です。
char s[] = "Hello world";
ここでは、s
私たちが希望する場合に上書きすることができます文字の配列は、あります。
char *s = "hello";
文字列リテラルは、このポインターs
が指すメモリ内のどこかにこれらの文字ブロックを作成するために使用されます。ここで、オブジェクトを変更することで、それが指しているオブジェクトを再度割り当てることができますが、文字列リテラルを指している限り、それが指している文字のブロックは変更できません。
さらに、読み取り専用の目的では両方の使用が同じであるため、[]
または*(<var> + <index>)
形式でインデックス付けすることにより、charにアクセスできることを考慮してください。
printf("%c", x[1]); //Prints r
そして:
printf("%c", *(x + 1)); //Prints r
明らかに、あなたがやろうとした場合
*(x + 1) = 'a';
読み取り専用メモリにアクセスしようとしているため、おそらくセグメンテーション違反が発生します。
x[1] = 'a';
segfaultと同じです(もちろんプラットフォームによって異なります)。
char *str = "Hello";
上記のstrは、プログラムのバイナリイメージにハードコードされているリテラル値「Hello」を指すように設定されています。これは、メモリ内で読み取り専用としてフラグが立てられています。つまり、この文字列リテラルの変更はすべて違法であり、セグメンテーション違反がスローされます。
char str[] = "Hello";
文字列をスタック上の新しく割り当てられたメモリにコピーします。したがって、それに変更を加えることは許可され、合法です。
means str[0] = 'M';
strを "Mello"に変更します。
詳細については、同様の質問をご覧ください。
「char s []」ではなく「char * s」で初期化された文字列に書き込むときに、セグメンテーション違反が発生するのはなぜですか?
ここでのコメントに照らして、次のことは明らかです。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