次のコードは、2行目でsegフォルトを受け取ります。
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
これは完璧に機能しますが、
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
MSVCおよびGCCでテスト済み。
次のコードは、2行目でsegフォルトを受け取ります。
char *str = "string";
str[0] = 'z'; // could be also written as *str = 'z'
printf("%s\n", str);
これは完璧に機能しますが、
char str[] = "string";
str[0] = 'z';
printf("%s\n", str);
MSVCおよびGCCでテスト済み。
回答:
C FAQ、質問1.32を参照してください
Q:これらの初期化の違いは何ですか?
char a[] = "string literal";
char *p = "string literal";
新しい値をに割り当てようとすると、プログラムがクラッシュしp[i]
ます。A:文字列リテラル(Cソースで二重引用符で囲まれた文字列の正式な用語)は、2つのわずかに異なる方法で使用できます。
- の宣言と同様に、charの配列の初期化子として、その配列の文字
char a[]
の初期値(および必要に応じてそのサイズ)を指定します。- それ以外の場所では、名前のない静的な文字配列になり、この名前のない配列は読み取り専用メモリに格納される可能性があるため、必ずしも変更する必要はありません。式のコンテキストでは、通常どおり、配列は一度にポインターに変換されます(セクション6を参照)。2番目の宣言は、名前のない配列の最初の要素を指すようにpを初期化します。
一部のコンパイラには、文字列リテラルが書き込み可能かどうかを制御するスイッチがあります(古いコードをコンパイルするため)。一部のコンパイラには、文字列リテラルをconst charの配列として正式に処理するオプションがあります(エラーをより適切にキャッチするため)。
mprotect
読み取り専用保護を使用してこれを行う方法もあります(ここを参照)。
通常、文字列リテラルは、プログラムの実行時に読み取り専用メモリに格納されます。これは、文字列定数を誤って変更しないようにするためです。最初の例で"string"
は、は読み取り専用メモリに格納され*str
、最初の文字を指します。最初の文字をに変更しようとすると、セグメンテーション違反が発生します'z'
。
2番目の例では、文字列"string"
はコンパイラによって読み取り専用のホームから配列にコピーされstr[]
ます。その後、最初の文字の変更が許可されます。これを確認するには、それぞれのアドレスを印刷します。
printf("%p", str);
また、str
2番目の例でのサイズを出力すると、コンパイラが7バイトを割り当てたことを示します。
printf("%d", sizeof(str));
これらの答えのほとんどは正しいですが、もう少し明確にするために...
人々が言及している「読み取り専用メモリ」は、ASM用語でのテキストセグメントです。これは、命令が読み込まれるメモリ内の同じ場所です。これは、セキュリティなどの明らかな理由により読み取り専用です。文字列に初期化されたchar *を作成すると、文字列データはテキストセグメントにコンパイルされ、プログラムはポインタを初期化してテキストセグメントを指すようにします。だからそれを変えようとするなら、カブーム。セグフォルト。
配列として記述された場合、コンパイラは初期化された文字列データを代わりにデータセグメントに配置します。これは、グローバル変数などが存在するのと同じ場所です。データセグメントに命令がないため、このメモリは変更可能です。今回コンパイラーが文字配列(まだchar *である)を初期化するときは、実行時に安全に変更できるテキストセグメントではなく、データセグメントを指しています。
文字列に書き込むときにセグメンテーション違反が発生するのはなぜですか?
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では、1つまたは複数の文字列リテラルから生成される各マルチバイト文字シーケンスに、値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のタイプ "array of 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
ます。
同じことをする場合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
最初のコードでは、「string」は文字列定数であり、文字列定数は読み取り専用メモリに配置されることが多いため、変更しないでください。「str」は定数を変更するために使用されているポインタです。
2番目のコードでは、 "string"は配列初期化子であり、
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
「str」はスタックに割り当てられた配列で、自由に変更できます。
str
がグローバルまたはの場合はデータセグメントstatic
。
"whatever"
最初の例のコンテキストでのタイプはconst char *
(非const char *に割り当てた場合でも)であるため、書き込みを試みてはなりません。
コンパイラは、文字列をメモリの読み取り専用部分に配置することでこれを強制しているため、文字列に書き込むとsegfaultが生成されます。
このエラーまたは問題を理解するには、まずポインターと配列の違いを知っておく必要がありますので、ここで最初にそれらの違いを説明します
char strarray[] = "hello";
メモリ配列は連続したメモリセルに格納され[h][e][l][l][o][\0] =>[]
、1文字のバイトサイズのメモリセルとして格納されます。この連続したメモリセルには、strarray hereという名前でアクセスできます。ここstrarray
では、初期化された文字列のすべての文字を含む文字列配列自体。ここで"hello"
は、インデックス値で各文字にアクセスすることで、メモリの内容を簡単に変更できます。
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
とその値がに変更された'm'
ため、strarray値は"mello"
。
文字ごとに文字列配列の内容を変更できますが、他の文字列を直接初期化することはできません。 strarray="new string"
無効
ポインタがメモリ内のメモリ位置を指していることは誰もが知っているように、初期化されていないポインタはランダムなメモリ位置を指し、初期化後は特定のメモリ位置を指します。
char *ptr = "hello";
ここで、ポインターptrは"hello"
、読み取り専用メモリ(ROM)に格納されている定数文字列である文字列に初期化されます。"hello"
、ROMに格納されているため変更できません。
そしてptrはスタックセクションに保存され、定数文字列を指します "hello"
読み取り専用メモリにアクセスできないため、ptr [0] = 'm'は無効です
しかし、ptrは他の文字列値に直接初期化できます。これは単なるポインタであるため、データ型の変数の任意のメモリアドレスを指すことができるためです。
ptr="new string"; is valid
char *str = "string";
コンパイラーが実行可能ファイルの変更不可能な部分に配置する文字列リテラルへのポインターを割り当てます。
char str[] = "string";
変更可能なローカル配列を割り当てて初期化します
int *b = {1,2,3)
ように書けchar *s = "HelloWorld"
ますか?
@matliがリンクしたCのFAQには言及されていますが、ここにはまだ誰もいません。そのため、明確にするために、文字配列(ソースで二重引用符で囲まれた文字列)が文字配列の初期化以外の場所で使用されている場合(つまり、@ Markの2番目の例は正しく機能します)。その文字列は、コンパイラーによって特別な静的文字列テーブルに格納されます。これは、本質的に匿名である(変数 "name"を持たない)グローバル静的変数(もちろん読み取り専用)を作成するのと同じです。 ")。読み取り専用の部分が重要な部分であり、そしてなぜ、@マークの最初のコード例のセグメンテーションフォルトです。
int *b = {1,2,3)
ように書けchar *s = "HelloWorld"
ますか?
の
char *str = "string";
lineはポインタを定義し、それをリテラル文字列にポイントします。リテラル文字列は書き込み可能ではないので、次のようにすると:
str[0] = 'z';
ワンセグ障害が発生します。一部のプラットフォームでは、リテラルが書き込み可能なメモリにあるため、セグメンテーション違反は表示されませんが、これは無効なコード(未定義の動作を引き起こす)です。
この線:
char str[] = "string";
文字の配列を割り当て、リテラル文字列をその配列にコピーします。その配列は完全に書き込み可能であるため、その後の更新は問題ありません。
int *b = {1,2,3)
ように書けchar *s = "HelloWorld"
ますか?
// create a string constant like this - will be read only
char *str_p;
str_p = "String constant";
// create an array of characters like this
char *arr_p;
char arr[] = "String in an array";
arr_p = &arr[0];
// now we try to change a character in the array first, this will work
*arr_p = 'E';
// lets try to change the first character of the string contant
*str_p = 'G'; // this will result in a segmentation fault. Comment it out to work.
/*-----------------------------------------------------------------------------
* String constants can't be modified. A segmentation fault is the result,
* because most operating systems will not allow a write
* operation on read only memory.
*-----------------------------------------------------------------------------*/
//print both strings to see if they have changed
printf("%s\n", str_p); //print the string without a variable
printf("%s\n", arr_p); //print the string, which is in an array.
そもそもstr
はを指すポインタです"string"
。コンパイラは、文字列リテラルをメモリ内の書き込みできない場所に配置できますが、読み取ることはできます。(をに割り当てているので、これは本当に警告をトリガーするはずでしconst char *
たchar *
でし。警告を無効にしましたか、それとも単に無視しましたか?)
2番目の場所では、完全なアクセス権を持つメモリである配列を作成し、それをで初期化してい"string"
ます。あなたはchar[7]
(文字用に6つ、終端の '\ 0'用に1つ)を作成し、それを使って好きなことを何でもします。
const
プレフィックスは変数を読み取り専用にします
char [N]
ではなくタイプなconst char [N]
ので、警告はありません。(少なくともを渡すことで、gccで変更できます-Wwrite-strings
。)
文字列は、
char a[] = "string literal copied to stack";
char *p = "string literal referenced by p";
最初のケースでは、 'a'がスコープに入ったときにリテラルがコピーされます。ここで、「a」はスタックで定義された配列です。これは、文字列がスタック上に作成され、そのデータがコード(テキスト)メモリからコピーされることを意味します。これは通常読み取り専用です(これは実装固有です。コンパイラは読み取り専用プログラムデータを読み取り書き込み可能なメモリに配置することもできます) )。
2番目のケースでは、pはスタック(ローカルスコープ)で定義され、他の場所に格納されている文字列リテラル(プログラムデータまたはテキスト)を参照するポインターです。通常、そのようなメモリを変更することは良い習慣ではなく、推奨されません。
セグメンテーション違反は、アクセスできないメモリにアクセスしようとすると発生します。
char *str
変更不可能な文字列へのポインタです(segfaultが発生する理由)。
一方char str[]
、配列であり、変更可能です。