Zeichenfolgenliterale: Wohin gehen sie?

Lesezeit: 8 Minuten

Benutzeravatar von Chris Cooper
Chris Cooper

Ich interessiere mich dafür, wo Zeichenfolgenliterale zugewiesen/gespeichert werden.

Ich habe hier eine faszinierende Antwort gefunden, die lautet:

Die Inline-Definition eines Strings bettet die Daten tatsächlich in das Programm selbst ein und kann nicht geändert werden (einige Compiler erlauben dies durch einen cleveren 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 wird mein String-Literal aufbewahrt? Warum sollte ich nicht versuchen, es zu ändern? Unterscheidet sich die Implementierung je nach Plattform? Möchte jemand den “klugen Trick” näher erläutern?

Benutzeravatar von R Samuel Klatchko
R. Samuel Klatschko

Eine gängige Technik besteht darin, Zeichenfolgenliterale in den Abschnitt “Nur-Lese-Daten” zu stellen, der dem Prozessraum als schreibgeschützt zugeordnet wird (weshalb Sie ihn nicht ändern können).

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

Anstatt zu versuchen, einen Trick zu finden, um String-Literale änderbar zu machen (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.

  • Sie müssen jedoch auf einen Pufferüberlauf achten, wenn Sie Arrays für veränderliche Zeichenfolgen verwenden – schreiben Sie einfach eine Zeichenfolge, die länger als die Arraylänge ist (z foo = "hello" in diesem Fall) kann unbeabsichtigte Nebeneffekte verursachen … (vorausgesetzt, Sie weisen keinen Speicher mit neu zu new oder so)

    – Johnny

    26. September 2011 um 17:52 Uhr

  • Geht bei der Verwendung von Array-Strings in den Stapel oder woanders?

    – Suraj Jain

    26. Dezember 2016 um 11:32 Uhr

  • Können wir nicht verwenden char *p = "abc"; um veränderliche Zeichenfolgen zu erstellen, wie von @ChrisCooper anders gesagt

    – ajaysinghnegi

    27. Juli 2019 um 16:39 Uhr

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

Warum sollte ich nicht versuchen, es zu ändern?

Weil es ein undefiniertes Verhalten ist. Zitat aus C99 N1256-Entwurf 6.7.8/32 „Initialisierung“:

BEISPIEL 8: Die Deklaration

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

definiert “einfache” Char-Array-Objekte s und t deren Elemente mit Zeichenkettenliteralen initialisiert werden.

Diese Deklaration ist identisch mit

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

Der Inhalt der Arrays ist modifizierbar. Andererseits die Deklaration

char *p = "abc";

definiert p vom Typ „pointer to char“ und initialisiert es so, dass es auf ein Objekt vom Typ „array of const char“ der Länge 4 zeigt, dessen Elemente mit einem Zeichenfolgenliteral initialisiert werden. Wenn versucht wird, zu verwenden p um den 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, wo die .text Abschnitt der Objektdatei ausgegeben wird, der über Lese- und Ausführungsberechtigungen verfügt, aber 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

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

Die Zeichenfolge wird also in der gespeichert .rodata Sektion.

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

Das bedeutet, dass das standardmäßige Linker-Skript beide ausgibt .text und .rodata in ein Segment, das ausgeführt, aber nicht geändert werden kann (Flags = R E). Der Versuch, ein solches Segment zu ändern, führt zu einem Segfault in Linux.

Wenn wir dasselbe für tun char[]:

 char s[] = "abc";

wir erhalten:

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

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

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

Abhängig von dem System, für das Sie schreiben, und den Fähigkeiten des verwendeten ausführbaren Dateiformats können sie zusammen mit dem Programmcode im Textsegment gespeichert werden oder sie können ein separates Segment für initialisierte Daten haben.

Die Bestimmung der Details hängt auch von der Plattform ab – höchstwahrscheinlich beinhalten sie Tools, die Ihnen sagen können, wo sie platziert werden. Einige geben Ihnen sogar die Kontrolle über Details wie diese, wenn Sie es möchten (z. B. erlaubt Ihnen gnu ld, ein Skript bereitzustellen, das alles darüber informiert, wie Daten, Code usw. gruppiert werden).

  • Ich finde es unwahrscheinlich, dass die Zeichenfolgendaten direkt im .text-Segment gespeichert werden. Bei wirklich kurzen Literalen konnte ich sehen, wie der Compiler Code generiert, wie z movb $65, 8(%esp); movb $66, 9(%esp); movb $0, 10(%esp) für die Saite "AB"aber die meiste Zeit befindet es sich in einem Nicht-Code-Segment wie z .data oder .rodata oder dergleichen (abhängig davon, ob das Ziel schreibgeschützte Segmente unterstützt oder nicht).

    – Adam Rosenfield

    2. Oktober 2012 um 20:00 Uhr


  • 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, siehe ideone.com/FTs1Ig

    – Destruktor

    21. November 2015 um 14:48 Uhr

  • @AdamRosenfield: Wenn Sie sich irgendwann langweilen, sollten Sie sich (zum Beispiel) das ältere UNIX a.out-Format ansehen (z. B. freebsd.org/cgi/…). Eine Sache, die Sie schnell bemerken sollten, ist, dass es nur ein Datensegment unterstützt, das immer beschreibbar ist. Wenn Sie also schreibgeschützte Zeichenfolgenliterale wünschen, sind sie im Wesentlichen der einzige Ort, an dem sie vorhanden sind kann go ist das Textsegment (und ja, Linker taten damals häufig genau das).

    – Jerry Sarg

    8. August 2019 um 17:43 Uhr


  • Warum ist das? Warum legen Sie sie nicht wie jede andere lokale Variable in den Stack?

    – Carlitos_30

    24. Juli um 12:27 Uhr

  • @ Carlitos_30: Als stapelbasierte lokale Variable müssten sie immer noch von etwas initialisiert werden, um den richtigen Inhalt zu halten. Wenn Sie also eine lokale Variable mit dem richtigen Inhalt wünschen, verwenden Sie char foo[] = "whatever";und Sie erhalten ein lokales Array von char–das wird normalerweise irgendwo von einem tatsächlichen String-Literal initialisiert.

    – Jerry Sarg

    24. Juli um 17:28 Uhr

Benutzeravatar von Justicle
Gerechtigkeit

Zu Ihrer Information, nur die anderen Antworten sichern:

Der Standard: ISO/IEC 14882:2003 sagt:

2.13. Zeichenfolgenliterale

  1. […]Ein gewöhnliches Zeichenfolgenliteral hat den Typ „array of n const char” und statische Speicherdauer (3.7)

  2. Ob alle Zeichenfolgenliterale verschieden sind (d. h. in nicht überlappenden Objekten gespeichert sind) wird von der Implementierung definiert. Die Auswirkung des Versuchs, ein Zeichenfolgenliteral zu ändern, ist nicht definiert.

gcc macht a .rodata Abschnitt, der “irgendwo” im Adressraum zugeordnet wird und als schreibgeschützt markiert ist,

Visual C++ (cl.exe) macht ein .rdata Abschnitt für den gleichen Zweck.

Sie können sich die Ausgabe von ansehen dumpbin oder objdump (unter Linux), 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

  • Ich kann nicht sehen, wie ich den Rdata-Abschnitt mit objdump disassemblieren kann.

    – Benutzer2284570

    30. November 2015 um 10:26 Uhr

  • @ user2284570, das liegt daran, dass dieser Abschnitt keine Assembly enthält. Es enthält Daten.

    – Alex Budowski

    30. November 2015 um 11:21 Uhr

  • Es geht nur darum, eine besser lesbare Ausgabe zu erhalten. Ich meine, ich würde gerne Strings mit Disassemblierung inline bekommen, anstatt mich auf diese Abschnitte zu beziehen. (ähm weißt duprintf("some null terminated static string");Anstatt vonprintf(*address);in C)

    – Benutzer2284570

    30. November 2015 um 11:23 Uhr


Benutzeravatar von Parappa
Parappa

Es kommt auf die an Format von dir ausführbar. Wenn Sie Assembler programmieren, könnten Sie String-Literale in das Datensegment Ihres Assemblerprogramms einfügen. Ihr C-Compiler macht so etwas, aber es hängt alles davon ab, für welches System Ihre Binärdatei kompiliert wird.

  • Ich kann nicht sehen, wie ich den Rdata-Abschnitt mit objdump disassemblieren kann.

    – Benutzer2284570

    30. November 2015 um 10:26 Uhr

  • @ user2284570, das liegt daran, dass dieser Abschnitt keine Assembly enthält. Es enthält Daten.

    – Alex Budowski

    30. November 2015 um 11:21 Uhr

  • Es geht nur darum, eine besser lesbare Ausgabe zu erhalten. Ich meine, ich würde gerne Strings mit Disassemblierung inline bekommen, anstatt mich auf diese Abschnitte zu beziehen. (ähm weißt duprintf("some null terminated static string");Anstatt vonprintf(*address);in C)

    – Benutzer2284570

    30. November 2015 um 11:23 Uhr


Benutzeravatar von Yu Hao
Yu Hao

Zeichenfolgenliterale werden häufig dem Nur-Lese-Speicher zugewiesen, wodurch sie unveränderlich werden. Bei einigen Compilern ist die Modifikation jedoch durch einen “klugen Trick” möglich.. Und der schlaue Trick besteht darin, “einen Zeichenzeiger zu verwenden, der auf den Speicher zeigt”.. Denken Sie daran, dass einige Compiler dies möglicherweise nicht zulassen

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

1425350cookie-checkZeichenfolgenliterale: Wohin gehen sie?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy