Warum unterscheiden sich die Speicheradressen von String-Literalen unter Linux so stark von denen anderer?

Lesezeit: 8 Minuten

Benutzeravatar von Noidea
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"

    – Hagen von Eitzen

    20. November 2016 um 10:38 Uhr

Benutzeravatar von PSkocik
PSkocik

So ist der Prozessspeicher unter Linux aufgebaut (von http://www.thegeekstuff.com/2012/03/linux-processes-memory-layout/):

Layout des Linux-Prozessspeichers

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

$ cat /proc/self/maps #cat's memory map

gibt mir

00400000-0040b000 r-xp 00000000 fc:00 395465                             /bin/cat
0060a000-0060b000 r--p 0000a000 fc:00 395465                             /bin/cat
0060b000-0060d000 rw-p 0000b000 fc:00 395465                             /bin/cat
006e3000-00704000 rw-p 00000000 00:00 0                                  [heap]
3000000000-3000023000 r-xp 00000000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000222000-3000223000 r--p 00022000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000223000-3000224000 rw-p 00023000 fc:00 3026487                        /lib/x86_64-linux-gnu/ld-2.19.so
3000224000-3000225000 rw-p 00000000 00:00 0
3000400000-30005ba000 r-xp 00000000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30005ba000-30007ba000 ---p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007ba000-30007be000 r--p 001ba000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007be000-30007c0000 rw-p 001be000 fc:00 3026488                        /lib/x86_64-linux-gnu/libc-2.19.so
30007c0000-30007c5000 rw-p 00000000 00:00 0
7f49eda93000-7f49edd79000 r--p 00000000 fc:00 2104890                    /usr/lib/locale/locale-archive
7f49edd79000-7f49edd7c000 rw-p 00000000 00:00 0
7f49edda7000-7f49edda9000 rw-p 00000000 00:00 0
7ffdae393000-7ffdae3b5000 rw-p 00000000 00:00 0                          [stack]
7ffdae3e6000-7ffdae3e8000 r--p 00000000 00:00 0                          [vvar]
7ffdae3e8000-7ffdae3ea000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

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.

Benutzeravatar von Steve Summit
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

printf ("%p\n", (void *) &h);
printf ("%p\n", (void *) &i);

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

const char *cp=”Hallo Welt”;
const char *cp1=”Hallo Welt”;

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

1413920cookie-checkWarum unterscheiden sich die Speicheradressen von String-Literalen unter Linux so stark von denen anderer?

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

Privacy policy