177

文字列リテラルがどこに割り当て/保存されるかに興味があります。

私はここで1つの興味深い答えを見つけました:

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

しかし、それは気にしないと言っていることは言うまでもなく、C++と関係がありました。

気になります。= D

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

4

8 に答える 8

137

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

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

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

char foo[] = "...";

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

于 2010-04-07T04:16:22.070 に答える
64

なぜ私はそれを変えようとすべきではないのですか?

未定義の動作だからです。C99N1256ドラフト 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";

タイプ「pointertochar」で定義pし、要素が文字列リテラルで初期化される長さ4のタイプ「arrayofconstchar」のオブジェクトを指すように初期化します。配列の内容を変更するために使用しようとした場合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にダンプすることを意味します( )。このようなセグメントを変更しようとすると、Linuxでセグメンテーション違反が発生します。.rodataFlags = R E

同じことをするとchar[]

 char s[] = "abc";

私達は手に入れました:

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

したがって、スタックに格納され(に対して%rbp)、もちろん変更できます。

于 2015-06-05T09:07:22.573 に答える
56

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

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

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

于 2010-04-07T04:20:46.417 に答える
23

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

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

2.13。文字列リテラル

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

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

于 2011-05-26T18:01:38.760 に答える
15

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
于 2010-04-07T04:25:27.537 に答える
4

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

于 2010-04-07T04:19:27.427 に答える
3

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

char *tabHeader = "Sound";
*tabHeader = 'L';
printf("%s\n",tabHeader); // Displays "Lound"
于 2013-10-13T12:49:36.247 に答える
0

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

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

ここで、はすべてのセクションの完全なコンテンツを表示するように-s強制し、はオブジェクトファイルであり、一致する前に1行も印刷するように強制し(セクション名が表示されるようにするため)、検索する文字列リテラルです。objdumpmain.o-B 1grepstr

Windowsマシンでgccを使用し、1つの変数を次のmainように宣言します。

char *c = "whatever";

ランニング

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

戻り値

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
于 2015-11-10T09:47:43.747 に答える