Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: üäpöol 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?
-
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ß)
-
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.
-
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.
-
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
-
http://stackoverflow.com/questions/394767/pointer-arithmetic (http://stackoverflow.com/questions/394767/pointer-arithmetic) zweite antwort jetzt versteh ichs
-
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.
-
"volatile" sollte es wenigstens zu einer im Speicher gehaltenen Variable machen. Ob das nun garantiert im Stack ist, sei mal dahingestellt...
-
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.