文字列リテラル:どこに行くのですか?


161

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

ここで興味深い答えが1つ見つかりました。

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

しかし、それはC ++に関係していることは言うまでもありません。

気になります。= D

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

回答:


125

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

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

むしろ、文字列リテラルを変更可能にするトリックを理解しようとします(これはプラットフォームに大きく依存し、時間の経過とともに変更される可能性があります)。配列を使用するだけです。

char foo[] = "...";

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


5
はい、可変文字列が必要な場合は配列を使用します。ちょっと気になっただけ。ありがとう。
Chris Cooper

2
ただし、可変文字列に配列を使用する場合は、バッファオーバーフローに注意する必要があります。配列の長さよりも長い文字列を書き込むだけで(たとえばfoo = "hello"、この場合)、意図しない副作用が発生する可能性があります... new何かでメモリを割り当てる)
ジョニー2011

2
配列文字列を使用すると、スタックまたは他の場所に行きますか?
Suraj Jain

char *p = "abc";@ChrisCooperによる別の言い方で、可変文字列を作成するために使用できないのか
KPMG

52

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

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

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


1
文字列データが.textセグメントに直接格納される可能性は低いと思います。本当に短いリテラルのために、私は、次のようなコンパイラ生成コードを見ることができmovb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)、文字列のため"AB"、しかし、時間の大半は、それはのような非コードセグメントになります.dataか、.rodataまたはかどうか、ターゲットがサポートに応じて、(のような読み取り専用セグメント)。
Adam Rosenfield、2012年

プログラムの全期間にわたって文字列リテラルが有効である場合、静的オブジェクトの破棄中でも、文字列リテラルへのconst参照を返すことは有効ですか?このプログラムがランタイムエラーを表示する理由ideone.com/FTs1Igを
Destructor

@AdamRosenfield:飽きたら、(たとえば)レガシーUNIXのa.out形式(たとえば、freebsd.org / cgi /…)を確認することをお勧めします。すぐに気付くべきことの1つは、常に書き込み可能な1つのデータセグメントのみをサポートすることです。したがって、読み取り専用の文字列リテラルが必要な場合、基本的にそれら移動できる唯一の場所はテキストセグメントです(そして、はい、リンカーが頻繁にそうしたとき)。
ジェリーコフィン

48

それを変更しようとしないのはなぜですか?

未定義の動作だからです。C99 N1256ドラフト 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 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でsegfaultが発生します。

同じことをする場合char[]

 char s[] = "abc";

私達は手に入れました:

17:   c7 45 f0 61 62 63 00    movl   $0x636261,-0x10(%rbp)

そのため、(に関連して%rbp)スタックに格納され、もちろんそれを変更できます。


22

参考までに、他の回答をバックアップします。

規格:ISO / IEC 14882:2003によると:

2.13。文字列リテラル

  1. [...]通常の文字列リテラルは、タイプ「配列n const char」と静的ストレージ期間(3.7)を持っています

  2. すべての文字列リテラルが別個である(つまり、重複しないオブジェクトに格納されている)かどうかは、実装によって定義されます。文字列リテラルを変更しようとした場合の影響は定義されていません。


2
役立つ情報ですが、リンクはC ++向けですが、質問はcに
Grijesh Chauhan 2013

1
2.13で#2を確認しました。-Osオプション(サイズの最適化)を使用すると、gccは.rodataの文字列リテラルと重複します。
Peng Zhang、

14

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

1
objdumpでrdataセクションを逆アセンブリする方法がわかりません。
user2284570 2015年

@ user2284570、それはそのセクションにアセンブリが含まれていないためです。データが含まれています。
Alex Budovski、2015年

1
より読みやすい出力を得るための問題。つまり、これらのセクションのアドレスではなく、逆アセンブリで文字列をインライン化したいのです。(C printf("some null terminated static string");printf(*address);はなく知っているヘム)
user2284570 2015年

4

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


2

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

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

0

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

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

ここで、すべてのセクションの完全な内容を表示することを-s強制objdumpします。これmain.oはオブジェクトファイルであり、一致する前に1行を-B 1強制的grepに出力します(セクション名を確認できるようにするため)。str検索する文字列リテラルです。

Windowsマシンのgccで、1つの変数がmainlikeで宣言されている

char *c = "whatever";

ランニング

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

戻り値

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.