web-dev-qa-db-ja.com

文字列リテラル:それらはどこに行きますか?

文字列リテラルが割り当て/格納される場所に興味があります。

私は興味深い答えを見つけました here 、と言って:

文字列をインラインで定義すると、実際にはプログラム自体にデータが埋め込まれ、変更することはできません(一部のコンパイラは、これをスマートトリックで許可します。気にしないでください)。

しかし、それはC++に関係していました。言うまでもなく、わざわざ言わないでください。

迷惑です。 = D

だから私の質問は、文字列リテラルがどこでどのように保持されるのですか?なぜ変更しようとしないのですか?実装はプラットフォームによって異なりますか? 「スマートトリック」について詳しく説明したい人はいますか?

151
Chris Cooper

一般的な方法は、文字列リテラルを「読み取り専用データ」セクションに配置し、プロセス空間に読み取り専用としてマップすることです(これが変更できない理由です)。

プラットフォームによって異なります。たとえば、より単純なチップアーキテクチャでは、読み取り専用メモリセグメントがサポートされないため、データセグメントは書き込み可能になります。

むしろ、文字列リテラルを変更可能にするためのトリックを考えてみてください(プラットフォームに大きく依存し、時間とともに変化する可能性があります)、配列を使用するだけです:

char foo[] = "...";

コンパイラーは配列をリテラルから初期化するように調整し、配列を変更できます。

120

これに対する答えはありません。 CおよびC++標準では、文字列リテラルには静的な保存期間があり、変更しようとすると未定義の動作が発生し、同じ内容の複数の文字列リテラルは同じ記憶域を共有する場合と共有しない場合があります。

記述しているシステム、およびそれが使用する実行可能ファイル形式の機能に応じて、テキストセグメントにプログラムコードと一緒に保存されるか、初期化されたデータ用に別のセグメントを持つ場合があります。

詳細の決定は、プラットフォームによっても異なります。ほとんどの場合、どこに配置するかを示すツールが含まれます。必要に応じて、そのような詳細を制御できるものもあります(たとえば、gnu ldを使用すると、データ、コードなどのグループ化方法をすべて伝えるスクリプトを提供できます)

50
Jerry Coffin

なぜ変更しないのですか?

それは未定義の動作だからです。 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。文字列リテラル

  1. [...]通常の文字列リテラルの型は「array of n const char」であり、静的ストレージ期間(3.7)

  2. すべての文字列リテラルが異なる(つまり、重複しないオブジェクトに格納される)かどうかは、実装によって定義されます。文字列リテラルを変更しようとする効果は未定義です。

22
Justicle

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
14
Alex Budovski

executableformat に依存します。それについて考える1つの方法は、アセンブリプログラミングをしている場合、アセンブリプログラムのデータセグメントに文字列リテラルを配置することです。あなたのCコンパイラはそのようなことをしますが、それはすべてあなたがバイナリであるシステムがコンパイルされていることに依存します。

4
Parappa

文字列リテラルは頻繁に読み取り専用メモリに割り当てられ、不変になります。ただし、一部のコンパイラでは、「スマートトリック」によって変更が可能です。そして、スマートトリックは「メモリを指す文字ポインタを使用する」ことです。一部のコンパイラを思い出してください。

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
2
Sahil Jain

これはコンパイラごとに異なる可能性があるため、最良の方法は、検索された文字列リテラルのオブジェクトダンプをフィルタリングすることです。

objdump -s main.o | grep -B 1 str

ここで、-sobjdumpにすべてのセクションの完全なコンテンツを表示させ、main.oはオブジェクトファイル、-B 1grepに一致する前の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....
0
mihai