Cでは、次のように宣言に文字列リテラルを使用できます。
char s[] = "hello";
またはこのように:
char *s = "hello";
それでは違いは何ですか?コンパイル時と実行時の両方で、実際にストレージ期間に関して何が起こるかを知りたいです。
ここでの違いは
char *s = "Hello world";
"Hello world"
をメモリーの 読み取り専用部分 に置き、s
をそれへのポインターにすると、このメモリーへの書き込み操作はすべて無効になります。
しながら:
char s[] = "Hello world";
リテラル文字列を読み取り専用メモリに置き、その文字列をスタック上の新しく割り当てられたメモリにコピーします。このようにして
s[0] = 'J';
法的。
まず、関数の引数では、それらはまったく同じです。
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";
oneオブジェクト-char
と呼ばれるサイズ6のs
配列を作成し、値'h', 'e', 'l', 'l', 'o', '\0'
で初期化します。この配列がメモリ内で割り当てられる場所、およびその寿命は、宣言が現れる場所によって異なります。宣言が関数内にある場合、宣言されているブロックの最後まで有効であり、ほぼ確実にスタックに割り当てられます。関数の外にある場合、おそらくは、プログラムの実行時に実行可能ファイルから書き込み可能メモリにロードされる「初期化データセグメント」内に格納されます。走る。
一方、この宣言:
char *s ="hello";
twoオブジェクトを作成します:
'h', 'e', 'l', 'l', 'o', '\0'
を含む6個のchar
sの配列。名前はなく、静的ストレージ期間を持ちます(プログラムの存続期間中存続することを意味します);そしてs
と呼ばれる、char-to-char型の変数。名前のない読み取り専用配列の最初の文字の位置で初期化されます。名前のない読み取り専用配列は通常、プログラムの「テキスト」セグメントに配置されます。つまり、コード自体とともにディスクから読み取り専用メモリにロードされます。メモリ内の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
(C++ではconst char
)の12要素の配列です。つまり、プログラムの起動時に割り当てられ、プログラムが終了するまで割り当てられたままになります。文字列リテラルの内容を変更しようとすると、未定義の動作が発生します。
この線
char *s0 = "hello world";
自動保存期間を持つchar
へのポインタとしてs0
を定義し(変数s0
が宣言されているスコープにのみ存在することを意味します)、文字列リテラルの address をコピーします(この例では0x00008000
)。 s0
は文字列リテラルを指すので、それを変更しようとする関数の引数として使用してはいけません(例:strtok()
、strcat()
、strcpy()
など)。
この線
char s1[] = "hello world";
s1
を自動保存期間付きのchar
(長さは文字列リテラルから取られる)の12要素の配列として定義し、そのリテラルの contents をその配列にコピーします。メモリマップからわかるように、文字列"hello world"
のコピーが2つあります。違いは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「初期化」で説明されています。
文字型の配列は、オプションで中括弧で囲まれた文字列リテラルによって初期化できます。文字列リテラルの連続する文字(空きがある場合、または配列のサイズが不明な場合は終端のヌル文字を含む)は、配列の要素を初期化します。
だから、これは単なるショートカットです:
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では、1つまたは複数の文字列リテラルから生じる各マルチバイト文字シーケンスに、値ゼロのバイトまたはコードが追加されます。次に、マルチバイト文字シーケンスを使用して、シーケンスを格納するのに十分な静的ストレージの継続時間と長さの配列を初期化します。文字列リテラルの場合、配列要素はchar型であり、マルチバイト文字シーケンスの個々のバイトで初期化されます[...]
6これらの配列が明確であるかどうかは、その要素に適切な値がある場合には指定されていません。プログラムがそのような配列を変更しようとする場合、動作は未定義です。
6.7.8/32「初期化」に直接的な例を示します。
例8:宣言
char s[] = "abc", t[3] = "abc";
要素が文字列リテラルで初期化される「プレーン」文字配列オブジェクト
s
およびt
を定義します。この宣言は次と同じです
char s[] = { 'a', 'b', 'c', '\0' }, t[] = { 'a', 'b', 'c' };
配列の内容は変更可能です。一方、宣言
char *p = "abc";
型「charへのポインター」で
p
を定義し、長さ4の型「char of array」でオブジェクトが文字列リテラルで初期化されるオブジェクトを指すように初期化します。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
を保存します。
char[]
に対して同じことを行う場合:
char s[] = "abc";
私達は手に入れました:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
そのため、(%rbp
に関連して)スタックに保存されます。
ただし、デフォルトのリンカースクリプトは、.rodata
と.text
を同じセグメントに配置します。これらのセグメントは実行はできますが、書き込み権限はありません。これは次の場合に確認できます。
readelf -l a.out
を含む:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
char s[] = "hello";
s
を初期化子(5 + 1 char
s)を保持するのに十分な長さのchar
の配列であると宣言し、与えられた文字列リテラルのメンバを配列にコピーすることによって配列を初期化します。
char *s = "hello";
s
を1つ以上(この場合はmore)のchar
sへのポインタとして宣言し、リテラル"hello"
を含む固定(読み取り専用)の場所を直接指すようにします。
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';
読み取り専用メモリにアクセスしようとしているので、おそらくセグメンテーション違反が発生します。
ちなみに、あなたはそれらのサイズにも異なる値を与えます。
printf("sizeof s[] = %zu\n", sizeof(s)); //6
printf("sizeof *s = %zu\n", sizeof(s)); //4 or 8
上で述べたように、配列に対しては'\0'
が最後の要素として割り当てられます。
char *str = "Hello";
上記のstrは、プログラムのバイナリイメージ内でハードコードされているリテラル値 "Hello"を指すようにstrを設定しています。これは、メモリ内で読み取り専用として設定されています。
char str[] = "Hello";
文字列をスタック上の新しく割り当てられたメモリにコピーします。したがって、変更を加えることは許可されており、合法です。
means str[0] = 'M';
strを "Mello"に変更します。
詳細については、同様の質問をしてください。
"char * s"で初期化され、 "char s []"で初期化されていない文字列に書き込むと、セグメンテーション違反が発生するのはなぜですか。
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
ここでのコメントに照らして、それは明らかであるべきです:char * s = "hello";悪い考えであり、非常に狭い範囲で使用されるべきです。
これは、「正当性」が「良いこと」であることを指摘する良い機会かもしれません。いつでもどこでもあなたができることは、 "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-ant突然変異に関連したエラーはコンパイル時に捕らえられます。実行時のスペースやconstに対する速度の低下はありません。
(もちろん、あなたはC++コンパイラを使っているのでしょうか?)
- DBJ