文字列リテラルが割り当て/格納される場所に興味があります。
私は興味深い答えを見つけました here 、と言って:
文字列をインラインで定義すると、実際にはプログラム自体にデータが埋め込まれ、変更することはできません(一部のコンパイラは、これをスマートトリックで許可します。気にしないでください)。
しかし、それはC++に関係していました。言うまでもなく、わざわざ言わないでください。
迷惑です。 = D
だから私の質問は、文字列リテラルがどこでどのように保持されるのですか?なぜ変更しようとしないのですか?実装はプラットフォームによって異なりますか? 「スマートトリック」について詳しく説明したい人はいますか?
一般的な方法は、文字列リテラルを「読み取り専用データ」セクションに配置し、プロセス空間に読み取り専用としてマップすることです(これが変更できない理由です)。
プラットフォームによって異なります。たとえば、より単純なチップアーキテクチャでは、読み取り専用メモリセグメントがサポートされないため、データセグメントは書き込み可能になります。
むしろ、文字列リテラルを変更可能にするためのトリックを考えてみてください(プラットフォームに大きく依存し、時間とともに変化する可能性があります)、配列を使用するだけです:
char foo[] = "...";
コンパイラーは配列をリテラルから初期化するように調整し、配列を変更できます。
これに対する答えはありません。 CおよびC++標準では、文字列リテラルには静的な保存期間があり、変更しようとすると未定義の動作が発生し、同じ内容の複数の文字列リテラルは同じ記憶域を共有する場合と共有しない場合があります。
記述しているシステム、およびそれが使用する実行可能ファイル形式の機能に応じて、テキストセグメントにプログラムコードと一緒に保存されるか、初期化されたデータ用に別のセグメントを持つ場合があります。
詳細の決定は、プラットフォームによっても異なります。ほとんどの場合、どこに配置するかを示すツールが含まれます。必要に応じて、そのような詳細を制御できるものもあります(たとえば、gnu ldを使用すると、データ、コードなどのグループ化方法をすべて伝えるスクリプトを提供できます)
なぜ変更しないのですか?
それは未定義の動作だからです。 C99 N1256ドラフト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の配列」でオブジェクトが文字列リテラルで初期化されるオブジェクトを指すように初期化します。p
を使用して配列の内容を変更しようとした場合、動作は未定義です。
どこに行くのですか?
GCC 4.8 x86-64 ELF Ubuntu 14.04:
char s[]
:スタックchar *s
:.rodata
オブジェクトファイルのセクション.text
セクションがダンプされる同じセグメント。読み取りおよび実行権限はあるが、書き込み権限はありませんプログラム:
#include <stdio.h>
int main() {
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
したがって、文字列は.rodata
セクションに保存されます。
次に:
readelf -l a.out
含む(簡略化):
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x0000000000000704 0x0000000000000704 R E 200000
Section to Segment mapping:
Segment Sections...
02 .text .rodata
つまり、デフォルトのリンカースクリプトは、.text
と.rodata
の両方を、実行はできるが変更はできないセグメント(Flags = R E
)にダンプします。そのようなセグメントを変更しようとすると、Linuxでセグメンテーション違反が発生します。
char[]
に対して同じことを行う場合:
char s[] = "abc";
私達は手に入れました:
17: c7 45 f0 61 62 63 00 movl $0x636261,-0x10(%rbp)
そのため、(%rbp
に関連して)スタックに格納され、もちろん変更できます。
参考までに、他の回答をバックアップするだけです。
標準: ISO/IEC 14882:20 言う:
2.13。文字列リテラル
[...]通常の文字列リテラルの型は「array of
n const char
」であり、静的ストレージ期間(3.7)すべての文字列リテラルが異なる(つまり、重複しないオブジェクトに格納される)かどうかは、実装によって定義されます。文字列リテラルを変更しようとする効果は未定義です。
gccは.rodata
セクションを作成します。このセクションは、アドレス空間の「どこか」にマッピングされ、読み取り専用としてマークされます。
Visual C++(cl.exe
)は、同じ目的で.rdata
セクションを作成します。
dumpbin
またはobjdump
(Linuxの場合)からの出力を見て、実行可能ファイルのセクションを確認できます。
例えば。
>dumpbin vec1.exe
Microsoft (R) COFF/PE Dumper Version 8.00.50727.762
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file vec1.exe
File Type: EXECUTABLE IMAGE
Summary
4000 .data
5000 .rdata <-- here are strings and other read-only stuff.
14000 .text
executable の format に依存します。それについて考える1つの方法は、アセンブリプログラミングをしている場合、アセンブリプログラムのデータセグメントに文字列リテラルを配置することです。あなたのCコンパイラはそのようなことをしますが、それはすべてあなたがバイナリであるシステムがコンパイルされていることに依存します。
文字列リテラルは頻繁に読み取り専用メモリに割り当てられ、不変になります。ただし、一部のコンパイラでは、「スマートトリック」によって変更が可能です。そして、スマートトリックは「メモリを指す文字ポインタを使用する」ことです。一部のコンパイラを思い出してください。
char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
これはコンパイラごとに異なる可能性があるため、最良の方法は、検索された文字列リテラルのオブジェクトダンプをフィルタリングすることです。
objdump -s main.o | grep -B 1 str
ここで、-s
はobjdump
にすべてのセクションの完全なコンテンツを表示させ、main.o
はオブジェクトファイル、-B 1
はgrep
に一致する前の1行も印刷させます(セクション名が表示されるように)、str
は検索する文字列リテラルです。
Windowsマシン上のgccで、main
のように宣言された1つの変数
char *c = "whatever";
ランニング
objdump -s main.o | grep -B 1 whatever
返却値
Contents of section .rdata:
0000 77686174 65766572 00000000 whatever....