*Markus: Verständnisfrage zum Speicher

Hallo,

also irgendwie verstehe ich etwas bei der Speicherung von Daten nicht ganz.
Folgendes Miniprogramm:

  
#include <stdio.h>  
  
int main(void)    {  
int a;  
int b;  
  
printf("a:%x b:%x\n", &a, &b);  
printf("Distanz: %d\n", (b-a) );  
}  

Wieso ergibt b-a eine riesige Zahl? Sollte es nicht 4 ergeben?
Meine zweite Frage betrifft den Speicher. Würde ich nun Daten in eine Variable einlesen, mir die Adresse merken, das Programm beenden und wieder starten und die Daten von der vorher bekannten Adresse wieder auslesen, wären die Daten dann auf immer und ewig dort? (natürlich solange der PC läuft?) Ich frage es mich deswegen, da bei einem vollen RAM doch alte Daten gelöscht werden, damit der Computer Platz für neue Daten hat, und man sich somit des Vorhandenseins der Daten doch gar nicht sicher sein darf?

Markus.

--
http://www.apostrophitis.at
STANDAR_D_  - ist das wirklich so schwer?
  1. Hallo,

    Folgendes Miniprogramm:

    #include <stdio.h>

    int main(void)    {
    int a;
    int b;

    printf("a:%x b:%x\n", &a, &b);
    printf("Distanz: %d\n", (b-a) );
    }

    
    >   
    > Wieso ergibt b-a eine riesige Zahl? Sollte es nicht 4 ergeben?  
      
      äh, was steht denn in a und b drin? Was gibt der Inhalt von b - Inhalt von a?  
      
    gruss
    
    -- 
    Swiss Army Chainsaw  
      
    Terrorific!  
    Given a cow full of milk, should the milk un-cow itself, or should the cow milk itself?
    
    1. Hallo,

      äh, was steht denn in a und b drin? Was gibt der Inhalt von b - Inhalt von a?

      Ja, richtig und logisch eigentlich. Aber verwende ich &b - &a, bekomme ich -1 als Distanz? Wie kann ich nun die beiden Adressen subtrahieren? Irgendwie steht ich jetzt auf der Leitung?

      Markus.

      --
      http://www.apostrophitis.at
      STANDAR_D_  - ist das wirklich so schwer?
      1. Hallo Markus.

        Ja, richtig und logisch eigentlich. Aber verwende ich &b - &a, bekomme ich -1 als Distanz? Wie kann ich nun die beiden Adressen subtrahieren? Irgendwie steht ich jetzt auf der Leitung?

        Der Stack wächst üblicherweise "nach unten", d.h. von den höheren Speicheradressen zu den niedrigeren.

      2. Ähm... möglicherweise war diese Erklärung etwas _zu_ knapp. Deshalb nochmal etwas ausführlicher:

        Wenn du die Variablen in der Reihenfolge...

        int a;
        int b;

        ... anlegst, landen sie -- wie Martin schon erwähnte -- meist direkt hintereinander im Speicher. Der Stackpointer wandert beim Hinzufügen von Werten auf den Stack zu den _niedrigeren_ Speicheradressen hin. Wird also beim Eintritt in deine Funktion ein Stackframe angelegt, in dem auch die lokalen Variablen enthalten sind, liegen die eher angelegten Variablen bei höheren Speicheradressen:

        pushl %ebp
        movl  %esp, %ebp
        subl  $4, %esp    ; int a
        subl  $4, %esp    ; int b

        (Das wird üblicherweise natürlich in einem Schritt ausgeführt: subl $8, %esp.)

        Wenn du nun &b - &a berechnest, ziehst du also eine größere Zahl von einer (um vier) kleineren ab.

        1. Hallo Blaubart,

          pushl %ebp
          movl  %esp, %ebp
          subl  $4, %esp    ; int a
          subl  $4, %esp    ; int b

          was für einen exotischen Assembler verwendest du denn? Deine Schreibweise ist zumindest sehr merkwürdig. Aus den Registernamen schließe ich, dass wir von x86-Assembler reden. Da ist die übliche Schreibweise aber anders:

          push  ebp
             mov   ebp, esp
             sub   esp, 8

          Erstens:  Das '%' vor Registernamen habe ich noch nirgends gesehen.
          Zweitens: Ein 'l' im Mnemonic ist unüblich. Die Verarbeitungsbreite (8, 16 oder 32bit)
                    wird durch die Operanden hinreichend festgelegt.
          Drittens: Wozu das '$' vor Konstanten? Habe ich auch noch nie gesehen.
          Viertens: Der Zieloperand wird immer als erster geschrieben, danach der Quelloperand.

          Schönen Abend noch,
           Martin

          --
          Realität ist eine Illusion, die durch Unterversorgung des Körpers mit Alkohol entstehen kann.
          1. Hallo Martin,

            pushl %ebp
            movl  %esp, %ebp
            subl  $4, %esp    ; int a
            subl  $4, %esp    ; int b

            was für einen exotischen Assembler verwendest du denn?

            Das sieht stark nach dem GNU-Assembler aus. Ich mag die Schreibweise btw. auch nicht, NASM ist mir da lieber (bin gerade am Lernen :-)).

            Viele Grüße,
            Christian

            1. 'Nabend Christian.

              Ich mag die Schreibweise btw. auch nicht, NASM ist mir da lieber (bin gerade am Lernen :-)).

              Das hängt zu großen Teilen vermutlich auch davon ab, welche Schreibweise man als erste kennengelernt hat. Laß mich mal raten, mit welche du zu lenern angefangen hast... ;)

              Ich "kann" beide, habe aber häufiger mit dem GNU-Assembler/Debugger zu tun. Also sicher auch eine Gewohnheitsfrage.

          2. Hallo Martin.

            Das sind "Eigenarten" der Darstellung, wie ich sie von Arbeiten mit Gnu Debug gewohnt bin.

            1. Hi,

              Das sind "Eigenarten" der Darstellung, wie ich sie von Arbeiten mit Gnu Debug gewohnt bin.

              okay, danke. Im Hinblick auf mein "erstens" bis "drittens" will ich wohl glauben, dass die Notation da geringfügig anders ist. Aber die Vertauschung der Operanden-Reihenfolge gegenüber der ursprünglichen Intel-Schreibweise fände ich doch bedenklich. Da sind ja für Umsteiger Fehler geradezu vorprogrammiert!
              Bist du sicher, dass du die Reihenfolge z.B. beim mov oder sub richtig wiedergegeben hast? Mir kam die Intel-Reihenfolge (erst Ziel, dann Quelle) damals auch erst etwas eigentümlich vor; nachdem ich mich daran gewöhnt hatte, erschien mir das aber logisch, denn es ist dieselbe Reihenfolge wie etwa bei einer Zuweisung in C.

              So long,
               Martin

              --
              F: Was sagt die kleine Kerze zur großen Kerze?
              A: Ich gehe heute nacht aus!
              1. Hallo Martin,

                Bist du sicher, dass du die Reihenfolge z.B. beim mov oder sub richtig wiedergegeben hast?

                Ja, die ist beim GNU-Assembler leider genau anders herum.

                Viele Grüße,
                Christian

          3. Hallo, der Martin

            Vielleicht könnte das für dich interessant sein?
            AT&T Syntax versus INTEL Syntax
            (Ich habe zwar selber noch nicht mehr als insgesamt vielleicht 100 bis 200 Zeilen Assembler geschrieben, aber mal gehört, dass es da zwischen UNIX und Windows Welt Unterschiede gibt. Wenn ich mal viel Zeit hätte, würde ich mich gerne mal mit Assembler beschäftigen - und ja: ich bin der König der Konjunktive)

            liebe Grüße mbr

  2. Moin,

    #include <stdio.h>

    int main(void)    {
    int a;
    int b;

    printf("a:%x b:%x\n", &a, &b);
    printf("Distanz: %d\n", (b-a) );
    }

    
    >   
    > Wieso ergibt b-a eine riesige Zahl? Sollte es nicht 4 ergeben?  
      
    wie kommst du denn darauf? Wo initialisierst du denn a u. b?  
    Du weisst doch zur Berechnung gar nicht, was vorher in a u. b drinstand....  
      
    MfG,  
      Juan  
     
    
  3. Hi,

    Anmerkung 1:
    Wieso sollte b-a 4 ergeben? Du hast a und b nicht initialisiert, da steht also irgendein Schwachsinn drin, z.B. 47 und 11. Was dabei rauskommt kann variieren, du rechnest aber auf jeden Fall mit dem Inhalt. Woher kommt sonst deine Vermutung, denkst du an Speicheradressen? Wenn ja, nur weil du zwei Variablen direkt nacheinander deklarierst müssen diese im Speicher nicht nacheinander liegen, können sie, müssen sie aber nicht.

    Anmerkung 2:
    Der Speicher wird mit Programmende freigegeben. Wenn eine andere Anwendung welchen anfordert kann es also passieren, dass er zwischenzeitig wieder neu vermietet wurde. Wenn keine Anwendung das tut und du auf den Speicher wieder zugreifst, dann hast du eine Chance die Werte wieder zu bekommen. Genau aus diesem Grund sollte man auch nie Variablen uninitialisiert verwenden, du hast nämlich keine Ahnung was in der Speicherstelle aktuell drinsteht.

    MfG
    Rouven

    --
    -------------------
    ss:) zu:) ls:& fo:) de:< va:{ ch:? sh:) n4:( rl:? br:$ js:| ie:) fl:(
    1. Hallo,

      Anmerkung 2:
      Der Speicher wird mit Programmende freigegeben. [...]

      Meine Verwirrung entstand eigentlich deswegen, da man das Programm doch durch den Debugger laufen lassen kann. Dabei setzte ich den breakpoint auf "main" und lies $esp mal eine ganze Weile durchlaufen. Irgendwann mal erscheinen plötzlich die Umgebungsvariablen von Linux wie PATH, HOSTNAME, usw und ich denke mir, dass solche Variablen doch nicht überschrieben werden dürfen. Folgedessen dachte ich eigentlich daran, auch Daten auf diese Weise abzulegen, aber offensichtlich habe ich das jetzt mit dem Stack verwechselt, da esp doch der Stack ist, oder verwechsle ich da nun wieder etwas?
      Ich dachte daran, vielleicht dort irgendwie Daten reinzustopfen.

      Markus.

      --
      http://www.apostrophitis.at
      STANDAR_D_  - ist das wirklich so schwer?
      1. Hallo Markus,

        Folgedessen dachte ich eigentlich daran, auch Daten auf diese Weise abzulegen, aber offensichtlich habe ich das jetzt mit dem Stack verwechselt, da esp doch der Stack ist, oder verwechsle ich da nun wieder etwas?
        Ich dachte daran, vielleicht dort irgendwie Daten reinzustopfen.

        Also, wenn Du Dich für solche Interna interessierst, solltest Du Dir ein gutes Buch dazu durchlesen, z.B. http://www.drpaulcarter.com/pcasm/ (gibt's als PDF zum runterladen) Du scheinst da nämlich noch ziemlich verwirrt zu sein, was das angeht.

        Wo Umgebungsvariablen abgelegt werden, ist nicht klar definiert, definiert ist nach ANSI C lediglich, dass es einen globalen Zeiger »environ« gibt, der auf die richtige Stelle zeigt. Die können auf dem Stack und die können auch auf dem Heap sein. Bei Dir sind sie offensichtlich auf dem Stack. Wenn Du Umgebungsvariablen modifizieren willst, modfizierst Du jedoch nicht diesen Teil des Stacks - setenv() fordert einen neuen Speicherbereich auf dem Heap an (per malloc() o.ä.), in den die neue Umgebungsvariable reinkopiert wird und ändert dann den entsprechenden Zeiger des environ-Arrays - falls eine neue Variable dazukommt, wird gleichzeitig ein neuer Speicherbereich für die Zeigerliste selbst angefordert.

        Du kannst jedoch auch Daten auf dem Stack dynamisch ablegen. Dazu bieten viele UNIXoide Betriebsysteme eine Funktion alloca() an, die genauso wie malloc() aufgerufen wird. Allerdings ist auf einigen Systemen die Implementierung Buggy, auf anderen steht sie gar nicht zur Verfügung. Meines Wissens definieren aber weder POSIX noch SUS diese Funktion (zumindest hab ich im Stevens auf Anhieb keine derartige Angabe gefunden).

        Viele Grüße,
        Christian

        1. Hallo,

          danke für die Info.
          Mein Hauptanliegen ist eigentlich, Daten irgendwie in eine Art Zwischenablage zu kopieren. Deswegen dachte ich an das Ablegen der Daten irgendwo, wo sie geschützt sind.

          Markus.

          --
          http://www.apostrophitis.at
          STANDAR_D_  - ist das wirklich so schwer?
          1. Hallo Markus,

            danke für die Info.
            Mein Hauptanliegen ist eigentlich, Daten irgendwie in eine Art Zwischenablage zu kopieren. Deswegen dachte ich an das Ablegen der Daten irgendwo, wo sie geschützt sind.

            Wenn es nur für die Laufzeit des Programms sein soll: alloziere doch einfach Speicher auf dem Heap per malloc o.ä. Solange Du auf die Speicherbereiche kein free anwendest, bleiben diese solange das Programm läuft, erhalten.

            Wenn die Daten das Programm überdauern sollen, musst Du zwangsläufig auf Betriebsystemfunktionen zurückgreifen. Unter Linux könntest Du bspw. Shared Memory verwenden (shmctl, shmget, etc. sind die Funktionen, die Du dafür suchst), unter Windows gibt's mit Sicherheit etwas ähnliches.

            Was genau willst Du denn anstellen? Vielleicht können wir Dir bessere Tips geben, wenn wir genauer wissen, was Du anstellen willst.

            Viele Grüße,
            Christian

            1. Hallo,

              Was genau willst Du denn anstellen? Vielleicht können wir Dir bessere Tips geben, wenn wir genauer wissen, was Du anstellen willst.

              Ich dachte daran, eine Art Clipper zu schreiben, um häufig verwendete Strings parat zu haben (zB HTML-Code). Dabei wollte ich aber keine Dateien verwenden, da es sonst ziemlich schnell hier drin unorganisiert werden könnte, sondern eben pro Speicheradresse einer dieser Strings ablegen. Ich wollte so vorgehen, dass man das Programm zwischendurch eben beenden kann, und nachher aber wieder zu den Daten kommt. Dadurch, dass herumliegende Daten im RAM nicht "sicher" sind, dachte ich daran sie eben irgendwo anders abzulegen, wo sie unangetastet blieben.

              Markus.

              --
              http://www.apostrophitis.at
              STANDAR_D_  - ist das wirklich so schwer?
              1. Hallo Markus,

                Was genau willst Du denn anstellen? Vielleicht können wir Dir bessere Tips geben, wenn wir genauer wissen, was Du anstellen willst.

                Ich dachte daran, eine Art Clipper zu schreiben, um häufig verwendete Strings parat zu haben (zB HTML-Code). Dabei wollte ich aber keine Dateien verwenden, da es sonst ziemlich schnell hier drin unorganisiert werden könnte, sondern eben pro Speicheradresse einer dieser Strings ablegen. Ich wollte so vorgehen, dass man das Programm zwischendurch eben beenden kann, und nachher aber wieder zu den Daten kommt. Dadurch, dass herumliegende Daten im RAM nicht "sicher" sind, dachte ich daran sie eben irgendwo anders abzulegen, wo sie unangetastet blieben.

                Naja, wenn Du kein RAM willst, musst Du sie zwangsläufig in Dateien speichern (naja Du könntest noch direkt auf die Festplatte zugreifen bei entsprechenden Zugriffsrechten, aber der Vorteil gegenüber Dateien erschließt sich mir in dem Fall nicht). Du brauchst ja nicht unterschiedliche Dateien verwenden, sondern eine Datei, in der alle drin stehen - durch irgendwas getrennt halt. Eventuell suchst Du auch ne Standalone-Datenbank wie SQLite oder Berkeley DB.

                Viele Grüße,
                Christian

  4. Hallo Markus,

    #include <stdio.h>

    int main(void)    {
    int a;
    int b;

    printf("a:%x b:%x\n", &a, &b);
    printf("Distanz: %d\n", (b-a) );
    }

      
    
    > Wieso ergibt b-a eine riesige Zahl? Sollte es nicht 4 ergeben?  
      
    Nein, du bildest nur die Differenz zweier nicht initialisierter Variablen.  
    Aber aus der restlichen Darstellung \_vermute\_ ich, dass du in Wirklichkeit die Differenz der \_Speicheradressen\_ von a und b meinst.  
    Dann dürftest du aber nicht (b-a) schreiben, sondern (&b - &a). Und, oh Wunder, da kommt 1 heraus.  
      
    Merkwürdig? Nur auf den ersten Blick.  
      
    Bei der Zeigerarithmetik in C wird nämlich immer der Datentyp berücksichtigt. Habe ich zum Beispiel sowas hier:  
      
     int  k;  
     int \*p = &k;  
     p++;  
      
    dann sieht das so aus, als ob der Zeiger p (per Deklaration ein Zeiger auf int) zunächst auf den Speicherplatz verweist, in dem k abgelegt ist, dann um eins erhöht wird. Dieses "eins" ist aber auf CPU-Ebene in Wirklichkeit 1\*(sizeof(int) Bytes), also bei 32bit-Systemen in der Regel 4 Bytes.  
    Bei der Subtraktion von Zeigern, die du wahrscheinlich im Sinn hattest, wird das Prinzip umgekehrt angewandt. (&b-&a) aus deinem Beispiel ergibt intern zunächst 4 Bytes, das entspricht wieder genau 1\*sizeof(int), also ist das Endergebnis 1.  
      
    
    > Meine zweite Frage betrifft den Speicher. Würde ich nun Daten in eine Variable einlesen, mir die Adresse merken, das Programm beenden und wieder starten und die Daten von der vorher bekannten Adresse wieder auslesen, wären die Daten dann auf immer und ewig dort? (natürlich solange der PC läuft?)  
      
    Naja, bedingt. Wenn der Speicherbereich in der Zwischenzeit nicht von einem anderen Prozess benutzt wurde, müssten die Daten noch unverändert da sein. Aber da du die Speicherverwaltung des Betriebssystems nicht unter Kontrolle hast, ist dieser Fall eher unwahrscheinlich.  
      
    
    > Ich frage es mich deswegen, da bei einem vollen RAM doch alte Daten gelöscht werden, damit der Computer Platz für neue Daten hat, und man sich somit des Vorhandenseins der Daten doch gar nicht sicher sein darf?  
      
    Eben. Naja, "gelöscht" wird normalerweise nichts - ein freier Speicherbereich wird einfach wieder einem Programm zugewiesen, wenn es Speicher anfordert. Was in diesem Bereich noch für Datenreste liegen, weiß keiner. Es können aber durchaus noch Arbeitsdaten eines Programms sein, das vorher lief und diesen Speicher benutzt hat.  
      
    Ich hoffe, ich konnte etwas Klarheit schaffen. ;-)  
      
    Schönen Feierabend,  
     Martin  
    
    -- 
    Auf jeden Menschen auf der ganzen Welt entfallen statistisch gesehen etwa 3000 Spinnen, wie Wissenschaftler jetzt festgestellt haben.  
    Wer will meine haben? Denn ich will sie bstimmt nicht.
    
    1. Hi Martin,

      dann sieht das so aus, als ob der Zeiger p (per Deklaration ein Zeiger auf int) zunächst auf den Speicherplatz verweist, in dem k abgelegt ist, dann um eins erhöht wird. Dieses "eins" ist aber auf CPU-Ebene in Wirklichkeit 1*(sizeof(int) Bytes), also bei 32bit-Systemen in der Regel 4 Bytes.
      Bei der Subtraktion von Zeigern, die du wahrscheinlich im Sinn hattest, wird das Prinzip umgekehrt angewandt. (&b-&a) aus deinem Beispiel ergibt intern zunächst 4 Bytes, das entspricht wieder genau 1*sizeof(int), also ist das Endergebnis 1.

      Ich liege aber doch richtig, dass das Ergebnis von
      int a;
      int b;
      nicht notwendigerweise auf eine Differenz von 1 herausläuft, oder?
      Es wäre theoretisch denkbar, dass die beiden ints an völlig unterschiedlichen Stellen liegen, oder nicht?

      MfG
      Rouven

      --
      -------------------
      ss:) zu:) ls:& fo:) de:< va:{ ch:? sh:) n4:( rl:? br:$ js:| ie:) fl:(
      1. Hallo Rouven,

        Bei der Subtraktion von Zeigern, die du wahrscheinlich im Sinn hattest, wird das Prinzip umgekehrt angewandt. (&b-&a) aus deinem Beispiel ergibt intern zunächst 4 Bytes, das entspricht wieder genau 1*sizeof(int), also ist das Endergebnis 1.
        Ich liege aber doch richtig, dass das Ergebnis von
        int a;
        int b;
        nicht notwendigerweise auf eine Differenz von 1 herausläuft, oder?

        ähm, wenn du so fragst... nein, eine absolut sichere Erkenntnis ist das natürlich nicht. ;-)

        Es wäre theoretisch denkbar, dass die beiden ints an völlig unterschiedlichen Stellen liegen, oder nicht?

        Ja. Aber real existierende Compiler legen Variablen, die unmittelbar nacheinander deklariert werden, in der Regel auch auf direkt benachbarte  Speicheradressen. Eventuell zugriffsoptimiert, d.h. auf eine durch 2 oder 4 teilbare Adresse ausgerichtet, aber üblicherweise aufeinanderfolgend.

        So long,
         Martin

        --
        Lieber Blödeleien als blöde Laien.