Autor Thema: Über Zeiger auf nächste Variable zugreifen  (Gelesen 7077 mal)

üäpöol

  • Beiträge: 110
    • Profil anzeigen
Gespeichert
« am: 16. December 2012, 14:39 »
Hi,
ich versuche gerade über den Zeiger der ersten Variable auf den Inhalt der nächsten Variable zuzugreifen:
int main(){
short var1 = 10;
short var2 = 30;
long *_var2 = (&var1+1);
print(itoa((short)*_var2), 80, 0xF);
while(1);
return 0;
}
Wenn ich statt
long *_var2 = (&var1+1);
long *_var2 = (&var1);
schreibe, wird mir 10 ausgegeben.
So wird mir aber nicht 30 sondern 0 ausgegeben.
Weiß jemand, woran das liegt?

Danke im Voraus.


EDIT:
int main(){
short var1 = 10;
short var2 = 30;
unsigned long* _var2 = (unsigned long*)(&var1-1);
print(itoa((unsigned long)*_var2-0xA0000), 80, 0xF);
print(itoa((unsigned long)&var2), 160, 0xF);
while(1);
return 0;
}
So funktioniert das aus irgendeinem Grund. Kann's mir jemand erklären?
« Letzte Änderung: 16. December 2012, 15:48 von üäpöol »

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #1 am: 16. December 2012, 15:52 »
Also lokale Variablen werden ja auf dem Stack gespeichert. Weil der Stack ja nach unten in Richtung niedriger Speicheradressen wächst(http://de.wikipedia.org/wiki/Stapelspeicher Absatz Mikroprozessoren), zeigt dein Pointer auf den Anfang des Heaps des Programms.
Probier mal long *_var2 = (&var1-2);(ein short ist ja zwei Bytes groß)

üäpöol

  • Beiträge: 110
    • Profil anzeigen
Gespeichert
« Antwort #2 am: 16. December 2012, 16:11 »
Hm, also bei dem Code
int main(){
        short var1 = 10;
short var2 = 30;
long* _var2 = (long*)(&var1-2);
print(itoa((long)*_var2), 80, 0xF);
while(1);
return 0;
}
wird 27868 ausgegeben.
Ich habe keinen blassen Schimmer, woher das kommt.

XanClic

  • Beiträge: 261
    • Profil anzeigen
    • github
Gespeichert
« Antwort #3 am: 16. December 2012, 21:59 »
Das kann von vielem kommen. Unter anderem daher, dass der Compiler überhaupt nicht verpflichtet ist, irgendwas auf den Stack zu legen. Er kann die Werte auch alle in Registern halten, wenn ihm danach ist. Und da du var2 überhaupt nicht benutzt, sondern nur initialisierst, ist es sogar wahrscheinlich, dass diese Variable komplett wegoptimiert wird und damit im Programm gar nicht existiert.

Desweiteren ist es dem Compiler natürlich auch freigestellt, die Variablen auf dem Stack zu ordnen, wie er das will. Ob var2 nun über var1 oder darunter liegt, ob es direkt darunter liegt oder 42 Bytes dazwischen frei sind – das kannst du nicht wirklich beeinflussen.

Sehen wir uns den objdump mal an, der bei gcc -m32 rauskommt (mit einem printf statt des prints):
8048405:       66 c7 44 24 16 0a 00    mov    WORD PTR [esp+0x16],0xa
804840c:       66 c7 44 24 1e 1e 00    mov    WORD PTR [esp+0x1e],0x1e
8048413:       8d 44 24 16             lea    eax,[esp+0x16]
8048417:       83 e8 04                sub    eax,0x4
804841a:       89 44 24 18             mov    DWORD PTR [esp+0x18],eax
804841e:       8b 44 24 18             mov    eax,DWORD PTR [esp+0x18]
8048422:       8b 00                   mov    eax,DWORD PTR [eax]
8048424:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
8048428:       c7 04 24 d0 84 04 08    mov    DWORD PTR [esp],0x80484d0
804842f:       e8 9c fe ff ff          call   80482d0 <printf@plt>

Erstmal werden also beide Variablen auf den Stack gelegt, da hast du sogar Glück. var1 liegt an [esp+0x16], var2 an [esp+0x1e]. Also liegt var2 hier mal über var1, dazwischen sind 6 Bytes Platz.

Dann wird die &var1 nach eax geladen, anschließend wird 4 abgezogen („&var1 - 2“ ist dank Pointerarithmetik das gleiche wie „(short *)((uintptr_t)&var1 - 4)“; es entspricht eben „&(&var1)[-2]“). Das heißt, eax zeigt dann auf [esp+12]. Da liegt nichts, also, undefinierte Daten. An 8048422 werden diese Daten dann geladen und im nächsten Befehl für printf bereitgelegt. Bei mir liegt da übrigens 63347.

Dein Problem hier ist also erstens die Pointerarithmetik (vermutlich willst du eher „&var1 - 1“) und zweitens die generelle Tatsache, dass nur der Compiler weiß, wie der Stack tatsächlich aussieht, also was überhaupt draufliegt, und wenn es da ist, wo es ist.


Mit -O3 ist das Ergebnis übrigens noch deutlich kürzer:
8048309:       8b 44 24 1a             mov    eax,DWORD PTR [esp+0x1a]
804830d:       c7 04 24 b0 84 04 08    mov    DWORD PTR [esp],0x80484b0
8048314:       89 44 24 04             mov    DWORD PTR [esp+0x4],eax
8048318:       e8 b3 ff ff ff          call   80482d0 <printf@plt>

Hier weiß gcc gleich, dass es vollkommen egal ist, welche Werte var1 und var2 annehmen, weil die eh nie von Belang sind (da du mit der Pointerarithmetik sowieso aus dem Bereich von var1 rausgehst und es wie gesagt nicht definiert ist, ob du so auf var2 landest). Damit liest er einfach den Wert von irgendwo auf dem Stack ([esp+0x1a] deutet darauf hin, dass var1 somit an [esp+0x1e] liegen würde, wenn sein Wert denn relevant wäre) und gibt den aus.
« Letzte Änderung: 16. December 2012, 22:01 von XanClic »

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #4 am: 16. December 2012, 23:09 »
Das mit den optimierungen ist mir bekannt(das ablegen Auf dem stack kann man aber auch mit auto short var1 erzwingen oder mit gcc -O0 )

Das mit der pointer arithmetik versteh ich nicht :( hilf bitte

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
« Antwort #5 am: 16. December 2012, 23:45 »

XanClic

  • Beiträge: 261
    • Profil anzeigen
    • github
Gespeichert
« Antwort #6 am: 17. December 2012, 00:02 »
das ablegen Auf dem stack kann man aber auch mit auto short var1 erzwingen
Ich würde behaupten, auto ist so, als würde man es weglassen (ist also die Default Storage Class). Wenn ich das benutze, erzwingt das im -O3-Fall auch gar nichts.

oder mit gcc -O0
Praktisch schon, theoretisch garantiert dir immer noch keiner, dass es wirklich draufliegt. Es ist halt undefiniert. Behaupte ich.

Svenska

  • Beiträge: 1 792
    • Profil anzeigen
Gespeichert
« Antwort #7 am: 17. December 2012, 02:19 »
"volatile" sollte es wenigstens zu einer im Speicher gehaltenen Variable machen. Ob das nun garantiert im Stack ist, sei mal dahingestellt...

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
« Antwort #8 am: 17. December 2012, 09:50 »
Wenn es darum geht, wozu man den Compiler zwingen kann, dann ist halt doch wieder der C-Standard entscheidend. Und der sagt eindeutig, dass das, was hier veranstaltet wird, undefiniertes Verhalten ist. Auch mit auto, volatile oder allen anderen Schlüsselwörtern, die es noch so gibt.

Sprich, wenn das Programm so kompiliert wird, dass es erst Jingle Bells spielt und danach alle Dateien auf der Platte löscht, dann kann das immer noch ein standardkonformer Compiler gewesen sein.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

 

Einloggen