“ char * s”で初期化された文字列に書き込むときにセグメンテーションエラーが発生するのはなぜですか。ただし、“ char s []&#8221 ;?
-
03-07-2019 - |
質問
次のコードは、2行目でセグエラーを受け取ります。
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&quot ;;
char * p =" string literal&quot ;;
p [i]
に新しい値を割り当てようとすると、プログラムがクラッシュします。A :文字列リテラル(正式な用語 Cで二重引用符で囲まれた文字列の場合 ソース)わずかに2つで使用することができます さまざまな方法:
char a []
の宣言のように、charの配列の初期化子として、初期値を指定します その配列内の文字の(および、 必要に応じて、そのサイズ)。- それ以外の場所では、名前のない静的な文字の配列になり、 そして、この名前のない配列は保存されます 読み取り専用メモリ内、および したがって、必ずしもそうであるとは限らない 変更されました。式のコンテキストでは、 配列は一度にaに変換されます ポインタ、通常どおり(セクション6を参照)、 2番目の宣言はpを初期化します 名前のない配列の最初を指す 素子。
一部のコンパイラにはスイッチがあります 文字列リテラルを制御する 書き込み可能かどうか(古いコンパイル用 コード)、およびいくつかのオプションがあります 文字列リテラルを形式的にする const charの配列として扱われます(for より良いエラーキャッチ)。
他のヒント
通常、文字列リテラルは、プログラムの実行時に読み取り専用メモリに保存されます。これは、誤って文字列定数を変更しないようにするためです。最初の例では、" string"
は読み取り専用メモリに保存され、 * str
は最初の文字を指します。セグメンテーション違反は、最初の文字を 'z'
に変更しようとすると発生します。
2番目の例では、文字列" string"
は、コンパイラによって読み取り専用のホームから str []
コピーされます>配列。その後、最初の文字の変更が許可されます。これを確認するには、それぞれのアドレスを印刷します:
printf("%p", str);
また、2番目の例の str
のサイズを印刷すると、コンパイラーが7バイトを割り当てていることがわかります。
printf("%d", sizeof(str));
これらの回答のほとんどは正しいですが、もう少し明確にするために...
「読み取り専用メモリ」」人々が言及しているのは、ASM用語のテキストセグメントです。これは、命令がロードされるメモリ内の同じ場所です。これは、セキュリティなどの明らかな理由で読み取り専用です。文字列に初期化されたchar *を作成すると、文字列データはテキストセグメントにコンパイルされ、プログラムはテキストセグメントを指すようにポインターを初期化します。それを変更しようとすると、kaboom。セグフォール。
配列として書き込まれる場合、コンパイラは初期化された文字列データを代わりにデータセグメントに配置します。これは、グローバル変数などが存在する場所と同じです。データセグメントに命令がないため、このメモリは可変です。今回は、コンパイラーが文字配列(まだchar *のまま)を初期化するとき、テキストセグメントではなくデータセグメントを指しているため、実行時に安全に変更できます。
文字列への書き込み時にセグメンテーション違反が発生するのはなぜですか?
C99 N1256ドラフト
文字列リテラルには2つの異なる使用法があります:
-
char []
の初期化:char c[] = "abc";
これは「もっと魔法」であり、6.7.8 / 14で説明されている「初期化」:
文字型の配列は、文字列リテラルによって初期化できます。オプションで 中括弧で囲まれています。文字列リテラルの連続する文字( 空きがある場合、または配列のサイズが不明な場合はヌル文字を終了します) 配列の要素。
これは次のショートカットです:
char c[] = {'a', 'b', 'c', '\0'};
他の通常の配列と同様に、
c
は変更できます。 -
他のどこでも:を生成します:
- 名前なし
- charの配列文字列リテラルのタイプは何ですかCとC ++?
- 静的ストレージを使用
- 変更された場合にUBを提供します
だからあなたが書くとき:
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" String literals"で文書化されています:
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"で定義します。そして、「char of array」型のオブジェクトを指すように初期化します。長さが4で、その要素は文字列リテラルで初期化されます。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 char s[] = "abc";
x0,-0x8(%rbp)
f: 00
c: R_X86_64_32S .rodata
結論:GCCは、 .text
ではなく、 .rodata
セクションに char *
を保存します。
char []
について同じことを行う場合:
17: c7 45 f0 61 62 63 00 movl readelf -l a.out
x636261,-0x10(%rbp)
取得:
Section to Segment mapping:
Segment Sections...
02 .text .rodata
そのため、(%rbp
に関連して)スタックに保存されます。
ただし、デフォルトのリンカスクリプトは、 .rodata
と .text
を同じセグメントに配置します。これらのセグメントには実行権限はありますが、書き込み権限はありません。これは以下で確認できます:
次を含む:
<*>最初のコードでは、&quot; string&quot;は文字列定数であり、文字列定数は読み取り専用メモリに配置されることが多いため、変更しないでください。 &quot; str&quot;定数を変更するために使用されるポインターです。
2番目のコードでは、&quot; string&quot;は配列初期化子で、
char str[7] = { 's', 't', 'r', 'i', 'n', 'g', '\0' };
&quot; str&quot;スタックに割り当てられた配列であり、自由に変更できます。
最初の例のコンテキストでの&quot; whatever&quot;
のタイプは const char *
であるため(非const char *に割り当てた場合でも) 、つまり、書き込もうとしてはいけません。
コンパイラは、メモリの読み取り専用部分に文字列を配置することでこれを実施しているため、それに書き込むとセグメンテーション違反が発生します。
このエラーまたは問題を理解するには、まずポインタと配列の違いを知っておく必要があります だからここでまず、それらの違いを説明しました
文字列配列
char strarray[] = "hello";
メモリ配列は連続したメモリセルに格納され、 [h] [e] [l] [l] [o] [\ 0] =&gt; []
は1文字バイトとして格納されますサイズのメモリセル、およびこの連続メモリセルはstrarray hereという名前でアクセスできます。ここでは、文字列配列 strarray
自体に初期化された文字列のすべての文字が含まれます。この場合は、ここでは&quot; hello&quot ;
インデックス値で各文字にアクセスすることで、メモリの内容を簡単に変更できます
`strarray[0]='m'` it access character at index 0 which is 'h'in strarray
およびその値が 'm'
に変更されたため、strarray値は&quot; mello&quot;
;
ここでは、文字ごとに文字列を変更することで文字列配列の内容を変更できますが、 strarray =&quot;新しい文字列&quot;
が無効であるように、他の文字列を直接初期化することはできませんp>
ポインター
私たちは皆、ポインターがメモリー内のメモリー位置を指すことを知っているので、 初期化されていないポインタはランダムなメモリ位置を指すので、初期化後は特定のメモリ位置を指します
char *ptr = "hello";
ここでポインタptrは、読み取り専用メモリ(ROM)に格納されている定数文字列である&quot; hello&quot;
に初期化されているため、&quot; hello&quot;
はROMに保存されています
およびptrはスタックセクションに格納され、定数文字列&quot; hello&quot;
so ptr [0] = 'm'は、読み取り専用メモリにアクセスできないため無効です
しかし、ptrは単なるポインターであるため、他の文字列値に直接初期化でき、そのデータ型の変数の任意のメモリアドレスを指すことができます
ptr="new string"; is valid
char *str = "string";
上記では、 str
がプログラムのバイナリイメージにハードコーディングされているリテラル値&quot; string&quot;
を指すように設定します。メモリ内。
したがって、 str [0] =
は、アプリケーションの読み取り専用コードへの書き込みを試みています。これはおそらくコンパイラに依存していると思います。
char *str = "string";
文字列リテラルへのポインタを割り当て、コンパイラは実行可能ファイルの変更不可能な部分に挿入します。
char str[] = "string";
変更可能なローカル配列を割り当てて初期化します
@matliがリンクしているC FAQに言及していますが、ここには誰もまだ言及していません。そのため、明確にするために:文字列リテラル(ソース内の二重引用符付き文字列)が >文字配列を初期化するには(つまり:正常に動作する@Markの2番目の例)、その文字列はコンパイラーによって特別な静的文字列テーブルに保存されます。これはグローバルな静的変数(当然、読み取り専用)これは本質的に匿名です(変数&quot; name&quot;はありません)。 読み取り専用の部分は重要な部分であり、@ Markの最初のコード例がセグメンテーション違反を起こす理由です。
char *str = "string";
lineはポインターを定義し、それをリテラル文字列にポイントします。リテラル文字列は書き込み可能ではないため、次の場合:
str[0] = 'z';
セグメンテーション違反が発生します。一部のプラットフォームでは、リテラルは書き込み可能なメモリにあるため、セグメンテーション違反は表示されませんが、それでも無効なコードです(未定義の動作になります)。
行:
char str[] = "string";
文字の配列を割り当て、その配列にリテラル文字列をコピーします。これは完全に書き込み可能であるため、その後の更新は問題ありません。
&quot; string&quot;のような文字列リテラルおそらく、実行可能ファイルのアドレス空間に読み取り専用データとして割り当てられます(コンパイラを使用するか、使用します)。触れてみると、水着エリアにいるのが怖くなり、セグエラーで知らせてくれます。
最初の例では、そのconstデータへのポインターを取得しています。 2番目の例では、constデータのコピーで7文字の配列を初期化しています。
// 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
は&quot; string&quot;
を指すポインターです。コンパイラーは、書き込みできないが読み取りしかできないメモリー内の場所に文字列リテラルを置くことができます。 ( char *
を char *
に割り当てているため、これは本当に警告をトリガーするはずでした。警告を無効にしましたか、それとも無視しましたか? )
2番目に、完全なアクセス権を持っているメモリである配列を作成し、&quot; string&quot;
で初期化します。 char [7]
(文字用に6つ、終端の「\ 0」用に1つ)を作成し、それで好きなことをします。
文字列は次のように仮定します
char a[] = "string literal copied to stack";
char *p = "string literal referenced by p";
最初のケースでは、 'a'がスコープに入ったときにリテラルがコピーされます。ここで、「a」はスタックで定義された配列です。文字列がスタック上に作成され、そのデータがコード(テキスト)メモリからコピーされることを意味します。これは通常読み取り専用です(これは実装固有です。コンパイラはこの読み取り専用プログラムデータを読み取り/書き込み可能なメモリに配置することもできます) )。
2番目のケースでは、pはスタック(ローカルスコープ)で定義され、どこに保存されている文字列リテラル(プログラムデータまたはテキスト)を参照するポインターです。通常、そのようなメモリを変更することは良い習慣ではなく、推奨されません。
最初は変更できない1つの定数文字列です。 2番目は初期化された値を持つ配列であるため、変更できます。
セグメンテーション違反は、アクセスできないメモリにアクセスするときに発生します。
char * str
は、変更不可能な文字列へのポインタです(セグエラーが発生する理由)。
whereas char str []
は配列であり、変更可能です。