String-Literale: Wohin gehen sie?

161

Ich interessiere mich dafür, wo String-Literale zugewiesen / gespeichert werden.

Ich habe hier eine faszinierende Antwort gefunden :

Das Definieren einer Zeichenfolge inline bettet die Daten tatsächlich in das Programm selbst ein und kann nicht geändert werden (einige Compiler erlauben dies durch einen intelligenten Trick, stören Sie sich nicht).

Aber es hatte mit C ++ zu tun, ganz zu schweigen davon, dass es heißt, sich nicht darum zu kümmern.

Ich störe. = D.

Meine Frage ist also, wo und wie mein String-Literal aufbewahrt wird. Warum sollte ich nicht versuchen, es zu ändern? Variiert die Implementierung je nach Plattform? Möchte jemand den "intelligenten Trick" näher erläutern?

Chris Cooper
quelle

Antworten:

125

Eine übliche Technik besteht darin, Zeichenfolgenliterale in den Abschnitt "Nur-Lese-Daten" einzufügen, der als Nur-Lese-Daten in den Prozessbereich abgebildet wird (weshalb Sie ihn nicht ändern können).

Es variiert je nach Plattform. Beispielsweise unterstützen einfachere Chiparchitekturen möglicherweise keine schreibgeschützten Speichersegmente, sodass das Datensegment beschreibbar ist.

Versuchen Sie stattdessen, einen Trick zu finden, mit dem Zeichenfolgenliterale geändert werden können (dies hängt stark von Ihrer Plattform ab und kann sich im Laufe der Zeit ändern). Verwenden Sie einfach Arrays:

char foo[] = "...";

Der Compiler sorgt dafür, dass das Array aus dem Literal initialisiert wird, und Sie können das Array ändern.

R Samuel Klatchko
quelle
5
Ja, ich verwende Arrays, wenn ich veränderbare Zeichenfolgen haben möchte. Ich war nur neugierig. Vielen Dank.
Chris Cooper
2
Sie müssen jedoch auf einen Pufferüberlauf achten, wenn Sie Arrays für veränderbare Zeichenfolgen verwenden. Wenn Sie lediglich eine Zeichenfolge schreiben, die länger als die Array-Länge ist (z. B. foo = "hello"in diesem Fall), kann dies zu unbeabsichtigten Nebenwirkungen führen ... (vorausgesetzt, Sie sind nicht erneut Speicher mit newoder etwas
zuweisen
2
Geht bei Verwendung von Array-Zeichenfolgen in Stapel oder anderswo?
Suraj Jain
Können wir nicht verwenden char *p = "abc";, um veränderbare Zeichenfolgen zu erstellen, wie von @ChrisCooper
KPMG
52

Darauf gibt es keine Antwort. Die C- und C ++ - Standards besagen lediglich, dass Zeichenfolgenliterale eine statische Speicherdauer haben. Jeder Versuch, sie zu ändern, führt zu einem undefinierten Verhalten, und mehrere Zeichenfolgenliterale mit demselben Inhalt können denselben Speicher gemeinsam nutzen oder nicht.

Abhängig vom System, für das Sie schreiben, und den Funktionen des verwendeten ausführbaren Dateiformats werden sie möglicherweise zusammen mit dem Programmcode im Textsegment gespeichert oder verfügen über ein separates Segment für initialisierte Daten.

Die Ermittlung der Details hängt auch von der Plattform ab. Höchstwahrscheinlich sind Tools enthalten, mit denen Sie feststellen können, wo sie sich befinden. Einige geben Ihnen sogar die Kontrolle über solche Details, wenn Sie dies möchten (z. B. können Sie mit gnu ld ein Skript bereitstellen, in dem Sie alles über das Gruppieren von Daten, Code usw. erfahren).

Jerry Sarg
quelle
1
Ich halte es für unwahrscheinlich, dass die Zeichenfolgendaten direkt im Textsegment gespeichert werden. Für wirklich kurze Literale konnte ich sehen, dass der Compiler Code wie movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp)für die Zeichenfolge generiert "AB", aber die meiste Zeit befindet er sich in einem Nicht-Code-Segment wie .dataoder .rodataoder ähnlichem (abhängig davon, ob das Ziel unterstützt oder nicht) schreibgeschützte Segmente).
Adam Rosenfield
Wenn Zeichenfolgenliterale für die gesamte Dauer des Programms gültig sind, auch während der Zerstörung statischer Objekte, ist es dann gültig, eine konstante Referenz auf ein Zeichenfolgenliteral zurückzugeben? Warum dieses Programm Laufzeitfehler anzeigt,
Destructor
@AdamRosenfield: Wenn Sie sich irgendwann langweilen, sollten Sie sich (zum Beispiel) das alte UNIX a.out-Format (z . B. freebsd.org/cgi/… ) ansehen . Eine Sache, die Sie schnell bemerken sollten, ist, dass es nur ein Datensegment unterstützt, das immer beschreibbar ist. Also , wenn Sie schreibgeschützte Stringliterale, im Wesentlichen der Platz nur sie können den Textabschnitt gehen wird (und ja, zu der Zeit Linker häufig tat genau das).
Jerry Coffin
48

Warum sollte ich nicht versuchen, es zu ändern?

Weil es undefiniertes Verhalten ist. Zitat aus C99 N1256 Entwurf 6.7.8 / 32 "Initialisierung" :

BEISPIEL 8: Die Erklärung

char s[] = "abc", t[3] = "abc";

definiert "einfache" char-Array-Objekte sundt deren Elemente mit Zeichenfolge Literale initialisiert.

Diese Erklärung ist identisch mit

char s[] = { 'a', 'b', 'c', '\0' },
t[] = { 'a', 'b', 'c' };

Der Inhalt der Arrays kann geändert werden. Auf der anderen Seite die Erklärung

char *p = "abc";

definiert pmit dem Typ "Zeiger auf Zeichen" und initialisiert es so, dass es auf ein Objekt mit dem Typ "Array von Zeichen" mit der Länge 4 zeigt, dessen Elemente mit einem Zeichenfolgenliteral initialisiert werden. Wenn versucht wird, pden Inhalt des Arrays zu ändern, ist das Verhalten undefiniert.

Wohin gehen sie?

GCC 4.8 x86-64 ELF Ubuntu 14.04:

  • char s[]: Stapel
  • char *s::
    • .rodata Abschnitt der Objektdatei
    • Das gleiche Segment, in dem der .textAbschnitt der Objektdatei ausgegeben wird, der über Lese- und Ausführungsberechtigungen verfügt, jedoch nicht über Schreibberechtigungen

Programm:

#include <stdio.h>

int main() {
    char *s = "abc";
    printf("%s\n", s);
    return 0;
}

Kompilieren und dekompilieren:

gcc -ggdb -std=c99 -c main.c
objdump -Sr main.o

Die Ausgabe enthält:

 char *s = "abc";
8:  48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
f:  00 
        c: R_X86_64_32S .rodata

Der String wird also im gespeichert .rodata Abschnitt .

Dann:

readelf -l a.out

Enthält (vereinfacht):

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

Dies bedeutet, dass das Standard-Linker-Skript sowohl .textals auch .rodatain ein Segment kopiert, das ausgeführt, aber nicht geändert werden kann (Flags = R E ). Der Versuch, ein solches Segment zu ändern, führt unter Linux zu einem Segfault.

Wenn wir dasselbe tun für char[]:

 char s[] = "abc";

wir erhalten:

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

so wird es im Stapel gespeichert (relativ zu %rbp), und wir können es natürlich ändern.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
22

Zu Ihrer Information, nur die anderen Antworten sichern:

Die Norm: ISO / IEC 14882: 2003 lautet:

2.13. String-Literale

  1. [...] Ein gewöhnliches String-Literal hat den Typ "Array of n const char" und die statische Speicherdauer (3.7).

  2. Ob alle Zeichenfolgenliterale unterschiedlich sind (dh in nicht überlappenden Objekten gespeichert sind), ist implementierungsdefiniert. Der Versuch, ein Zeichenfolgenliteral zu ändern, ist nicht definiert.

Justicle
quelle
2
Hilfreiche Informationen, aber Hinweis Link ist für C ++, während Frage an c
Grijesh Chauhan
1
bestätigte # 2 in 2.13. Mit der Option -Os (für Größe optimieren) überlappt gcc Zeichenfolgenliterale in .rodata.
Peng Zhang
14

gcc erstellt einen .rodataAbschnitt, der "irgendwo" im Adressraum zugeordnet und als schreibgeschützt markiert wird.

Visual C ++ ( cl.exe) erstellt einen .rdataAbschnitt für denselben Zweck.

Sie können sich die Ausgabe von dumpbinoder objdump(unter Linux) ansehen, um die Abschnitte Ihrer ausführbaren Datei anzuzeigen.

Z.B

>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
Alex Budovski
quelle
1
Ich kann nicht sehen, wie ich mit objdump die Demontage des rdata-Abschnitts erreichen kann.
user2284570
@ user2284570, das liegt daran, dass dieser Abschnitt keine Assembly enthält. Es enthält Daten.
Alex Budovski
1
Nur eine Frage, um eine besser lesbare Ausgabe zu erhalten. Ich meine, ich möchte, dass Zeichenfolgen bei der Demontage eingefügt werden, anstatt an diese Abschnitte zu adressieren. (Saum, den Sie printf("some null terminated static string");anstelle von printf(*address);in C kennen)
user2284570
4

Dies hängt vom Format Ihrer ausführbaren Datei ab . Eine Möglichkeit, darüber nachzudenken, besteht darin, dass Sie bei der Assembly-Programmierung möglicherweise Zeichenfolgenliterale in das Datensegment Ihres Assembly-Programms einfügen. Ihr C-Compiler macht so etwas, aber alles hängt davon ab, für welches System Ihre Binärdatei kompiliert wird.

Parappa
quelle
2

String-Literale werden häufig dem Nur-Lese-Speicher zugewiesen, wodurch sie unveränderlich werden. Bei einigen Compilern ist eine Änderung jedoch durch einen "intelligenten Trick" möglich. Und der intelligente Trick besteht darin, "einen auf den Speicher zeigenden Zeichenzeiger zu verwenden". Denken Sie daran, dass einige Compiler dies möglicherweise nicht zulassen. Hier ist eine Demo

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

Da dies von Compiler zu Compiler unterschiedlich sein kann, besteht der beste Weg darin, einen Objektspeicherauszug nach dem gesuchten Zeichenfolgenliteral zu filtern:

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

Dabei wird -serzwungen objdump, den vollständigen Inhalt aller Abschnitte anzuzeigen. Dies main.oist die Objektdatei, -B 1erzwingt grepdas Drucken einer Zeile vor dem Abgleich (damit Sie den Abschnittsnamen sehen können) und strist das gesuchte Zeichenfolgenliteral.

Mit gcc auf einem Windows-Computer und einer in mainlike deklarierten Variablen

char *c = "whatever";

Laufen

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

kehrt zurück

Contents of section .rdata:
 0000 77686174 65766572 00000000           whatever....
mihai
quelle