Warum unterscheiden sich die Speicheradressen von String-Literalen unter Linux so stark von denen anderer?
Lesezeit: 8 Minuten
Keine Ahnung
Mir ist aufgefallen, dass String-Literale ganz andere Adressen im Speicher haben als andere Konstanten und Variablen (Linux OS): Sie haben viele führende Nullen (nicht gedruckt).
Beispiel:
const char *h = "Hi";
int i = 1;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) &i);
Ausgabe:
0x400634
0x7fffc1ef1a4c
Ich weiß, dass sie in der gespeichert sind .rodata Teil der ausführbaren Datei. Gibt es eine spezielle Art und Weise, wie das Betriebssystem danach damit umgeht, sodass die Literale in einem speziellen Speicherbereich (mit führenden Nullen) landen? Hat dieser Speicherort Vorteile oder ist etwas Besonderes daran?
Es liegt ganz beim Betriebssystem, wo es den Code lädt und wo es den Stack zuweist.
– Irgendein Programmierer-Typ
18. November 2016 um 12:55 Uhr
Offensichtlich implementierungsspezifisch, aber RO-Daten (Ihr Literal) werden häufig in separate Seiten geladen, die für das Auslösen von Ausnahmen beim Schreiben im geschützten Modus markiert sind. Bedeutung: Das Schreiben darauf löst eine strukturierte Ausnahme aus.
– WhozCraig
18. November 2016 um 13:00 Uhr
Betrifft Ihre Frage speziell Linux, gehostete Systeme (mit OS) im Allgemeinen oder auch freistehende Systeme (typischerweise eingebettet ohne OS)? Wenn nur Linux, sollten Sie hinzufügen [linux] Schild. Wenn etwas anderes, bitte klären.
– Benutzer694733
18. November 2016 um 13:01 Uhr
Ihre Frage ist von hinten nach vorne. Das wirst du finden alle Adressen haben ‘viele führende Nullen’ außer Adressen von lokalen Variablen, die sich auf dem Stapel befinden, der in Ihrem Fall von oben im Adressraum nach unten zugewiesen wird.
– Benutzer207421
18. November 2016 um 21:49 Uhr
Um Ihre Saite mehr wie Ihre zu haben int i = 1vielleicht möchten Sie es versuchen char h[] = "Hi"
Das .rodata Abschnitt ist ein schreibgeschützter Unterabschnitt der Initialisierte globale Daten Block. (Ein Abschnitt, der ELF ausführbare Dateien benennen .Daten ist sein beschreibbares Gegenstück für beschreibbare globale Werte, die mit Werten ungleich Null initialisiert wurden. Beschreibbare Globals, die mit Nullen initialisiert werden, gehen an die .bss Block. Mit Globals meine ich hier globale Variablen und alles statisch Variablen unabhängig von der Platzierung.)
Das Bild soll die Zahlenwerte Ihrer Adressen erklären.
Wenn Sie weiter nachforschen möchten, können Sie unter Linux die /proc/$pid/maps virtuelle Dateien, die das Speicherlayout laufender Prozesse beschreiben. Sie erhalten nicht die reservierten (mit einem Punkt beginnenden) ELF-Abschnittsnamen, aber Sie können erraten, aus welchem ELF-Abschnitt ein Speicherblock stammt, indem Sie sich seine Speicherschutz-Flags ansehen. Laufen zum Beispiel
Der Erste r-xp Block kam definitiv von .Text (ausführbarer Code), der erste r--p abblocken .rodataund die folgende rw– Blöcke ab .bss und .Daten. (Zwischen dem Heap- und dem Stack-Block befinden sich Blöcke, die vom dynamischen Linker aus dynamisch verknüpften Bibliotheken geladen werden.)
Notiz: Um der Norm zu entsprechen, sollten Sie die gießen int* zum "%p" zu (void*) oder das Verhalten ist undefiniert.
Danke, das ist nützlich! Aber wenn ich mehrere Prozesse habe, passiert dies immer noch. Es legt sie also nicht nacheinander an, sondern nimmt alle “Initialized Global Data” aus mehreren Prozessen und speichert sie zusammen?
– Keine Ahnung
18. November 2016 um 13:21 Uhr
@Noidea Verschiedene Prozesse haben unterschiedliche Adressräume. 0xDEADBEEF in einem Prozess ist (normalerweise) völlig unabhängig von 0xDEADBEEF in einem anderen. Es gibt einige offensichtliche kleinere Vorteile des obigen Layouts in Bezug auf Debugging und Blockwachstum (insbesondere für den Heap-Block, obwohl es keine große Sache ist, den Heap mit mmap zu fragmentieren, wenn er nicht mehr wachsen kann). Außerdem sind die tatsächlich zugeordneten Adressen aus Sicherheitsgründen normalerweise etwas zufällig.
– PSkočik
18. November 2016 um 13:40 Uhr
@Noidea: Verschmelzen Sie keine physischen Adressen (die Adressen im RAM entsprechen) mit virtuellen Speicheradressen (Adressen im Prozess). Es ist die Aufgabe der Speicherverwaltungseinheit um virtuell in physisch umzuwandeln, und alle von einem Prozess verwendeten Adressen werden über die MMU übersetzt. Jeder Prozess hat seine eigenen MMU-Tabellen, die vom Betriebssystem verwaltet werden.
– Eric Türme
18. November 2016 um 14:10 Uhr
Die Standard-Linker-Skripte mehrerer Plattformen werden ebenfalls zusammengeführt .rodata mit .text.
– Simon Richter
19. November 2016 um 17:05 Uhr
Das liegt daran, dass Zeichenfolgenliterale haben statische Speicherdauer. Das heißt, sie werden während des gesamten Programms leben. Solche Variablen können an einem speziellen Speicherplatz gespeichert werden, der sich weder auf dem sogenannten Heap noch auf dem Stack befindet. Daher die unterschiedlichen Adressen.
Steve Gipfel
Denken Sie daran, dass wo ein Zeiger war ist unterscheidet sich von wo ein Zeiger verweist auf. Ein realistischerer (Äpfel-zu-Äpfel) Vergleich wäre
Ich vermute, Sie werden das finden h und p ähnliche Adressen haben. Oder ein anderer, realistischerer Vergleich wäre
static int si = 123;
int *ip = &si;
printf ("%p\n", (void *) h);
printf ("%p\n", (void *) ip);
Ich vermute, Sie werden das finden h und ip weisen auf eine ähnliche Erinnerungsregion hin.
Nein, h ist bereits ein Zeiger auf Zeichen, also &h tut nichts brauchbares. Schreiben h und &i ist richtig, denn dann sind beide die Adressen der referenzierten Zeichenfolge und int beziehungsweise.
– Unterstrich_d
18. November 2016 um 16:41 Uhr
@underscore_d Ich denke, Sie haben die Frage und meine Antwort dann völlig falsch verstanden. Es gibt nichts „richtiges“ oder „falsches“ Schreiben h und &i; Das OP war nur verwirrt darüber, warum die tatsächlichen Adressen auf seinem System so unterschiedlich waren. Mein Punkt war, wenn Sie schreiben &h und &ioder h und ipsehen Sie wahrscheinlich mehr ähnliche Adressen, und diese Übung hilft Ihnen (hoffentlich) zu verstehen, warum die Zahlen in h und &i sind so unterschiedlich.
– Steve Summit
18. November 2016 um 16:52 Uhr
@SteveSummit Zeiger auf ein String-Literal wird eine weitere Stack-Variable sein. Aber ich habe mich gefragt, warum sich die Adresse eines String-Literals so sehr von den Adressen von Stack-Variablen unterscheidet. Nicht, warum die Adressen von zwei Stack-Variablen ähnlich sind;)
– Keine Ahnung
18. November 2016 um 17:16 Uhr
@Noidea Und jetzt wissen Sie es aus den anderen Antworten: weil Zeichenfolgenliterale niemals auf dem Stapel gespeichert werden.
– Steve Gipfel
18. November 2016 um 17:18 Uhr
@SteveSummit naja ich wusste schon, dass die nicht on Stack sind, weil die Adressen so unterschiedlich sind.
– Keine Ahnung
18. November 2016 um 17:29 Uhr
Bedenken Sie, dass Literale schreibgeschützte Variablen sind und dass es auch ein Konzept eines Literalpools gibt. Der Literal-Pool ist eine Sammlung der eindeutigen Literale des Programms, in der doppelte Konstanten verworfen werden, wenn Referenzen zu einer zusammengeführt werden.
Es gibt einen Literal-Pool für jede Quelle, und abhängig von der Ausgereiftheit des Link-/Bind-Programms können Literal-Pools nebeneinander platziert werden, um eine .rodata-Datei zu erstellen.
Es gibt auch keine Garantie dafür, dass der Literal-Pool schreibgeschützt ist. Sprache, obwohl Compiler-Designs es so behandeln.
Betrachten Sie mein Codefragment. ich könnte haben
Der gute Compiler wird das in diesem Quellcode erkennen, die schreibgeschützten Literale CP, CP1zeigen auf identische Zeichenfolgen und lassen cp1 auf das Literal von cp zeigen, wobei das zweite verworfen wird.
Noch ein Punkt. Der Literalpool kann ein Vielfaches von 256 Bytes oder ein anderer Wert sein. Wenn die Pooldaten kleiner als 256 Bytes sind, wird der Schlupf mit hexadezimalen Nullen aufgefüllt.
Verschiedene Compiler folgen gemeinsamen Entwicklungsstandards, die es ermöglichen, ein Modul mit zu kompilieren Cum mit einem kompilierten Modul verknüpft zu werden Assemblersprache oder andere Sprache. Die beiden Literal-Pools werden nacheinander in .rodata abgelegt.
printf ("%p\n", h); // h is the address of "Hi", which is in the rodata or other segments of the application.
printf ("%p\n", &i); // I think "i" is not a global variable, so &i is in the stack of main. The stack address is by convention in the top area of the memory space of the process.
Dies scheint die gestellte Frage nicht zu beantworten. Zur Erinnerung: Die Frage “Handhabt das Betriebssystem es speziell? Gibt es irgendwelche Vorteile dieser Art der Handhabung?” Ihre Antwort scheint diese Fragen nicht zu beantworten. Möchten Sie Ihre Antwort bearbeiten, um direkter auf die Frage einzugehen?
– DW
18. November 2016 um 15:58 Uhr
Dies scheint die gestellte Frage nicht zu beantworten. Zur Erinnerung: Die Frage “Handhabt das Betriebssystem es speziell? Gibt es irgendwelche Vorteile dieser Art der Handhabung?” Ihre Antwort scheint diese Fragen nicht zu beantworten. Möchten Sie Ihre Antwort bearbeiten, um direkter auf die Frage einzugehen?
– DW
18. November 2016 um 15:58 Uhr
14139200cookie-checkWarum unterscheiden sich die Speicheradressen von String-Literalen unter Linux so stark von denen anderer?yes
Es liegt ganz beim Betriebssystem, wo es den Code lädt und wo es den Stack zuweist.
– Irgendein Programmierer-Typ
18. November 2016 um 12:55 Uhr
Offensichtlich implementierungsspezifisch, aber RO-Daten (Ihr Literal) werden häufig in separate Seiten geladen, die für das Auslösen von Ausnahmen beim Schreiben im geschützten Modus markiert sind. Bedeutung: Das Schreiben darauf löst eine strukturierte Ausnahme aus.
– WhozCraig
18. November 2016 um 13:00 Uhr
Betrifft Ihre Frage speziell Linux, gehostete Systeme (mit OS) im Allgemeinen oder auch freistehende Systeme (typischerweise eingebettet ohne OS)? Wenn nur Linux, sollten Sie hinzufügen
[linux]
Schild. Wenn etwas anderes, bitte klären.– Benutzer694733
18. November 2016 um 13:01 Uhr
Ihre Frage ist von hinten nach vorne. Das wirst du finden alle Adressen haben ‘viele führende Nullen’ außer Adressen von lokalen Variablen, die sich auf dem Stapel befinden, der in Ihrem Fall von oben im Adressraum nach unten zugewiesen wird.
– Benutzer207421
18. November 2016 um 21:49 Uhr
Um Ihre Saite mehr wie Ihre zu haben
int i = 1
vielleicht möchten Sie es versuchenchar h[] = "Hi"
– Hagen von Eitzen
20. November 2016 um 10:38 Uhr