/*
  loes9-6.c
  Demo zur Speicherverwaltung beim Aufruf von Funktionen
*/


#include <stdio.h>

void f1(void)
{
  int n1;
  int n2;
  int *pn1 = &n1;
  int *pn2 = &n2;

  n1 = 1;
  n2 = 2;

  printf("f1: &n1 = %p, n1 = %6i\n", &n1, n1);
  printf("f1: &n2 = %p, n2 = %6i\n", &n2, n2);

}


void f2(void)
{
  int n1;
  int n2;
  int *pn1 = &n1;
  int *pn2 = &n2;

	/* n1 und n2 sind noch nicht definiert 
     (erzeugt bei üblichen Compiler-Einstellungen Warnung) */

  printf("f2: &n1 = %p, n1 = %6i\n", &n1, n1);
  printf("f2: &n2 = %p, n2 = %6i\n", &n2, n2);

  n1 = 3;
  n2 = 4;

  printf("f2: &n1 = %p, n1 = %6i\n", &n1, n1);
  printf("f2: &n2 = %p, n2 = %6i\n", &n2, n2);
}


int main(void)
{
  printf("main = %p\n  f1 = %p\n  f2 = %p\n", main, f1, f2);
  f1();
  f2();
  return 0;
}



Hauptzweck der Demo ist zu zeigen, wie die Variablen einer Funktion beim Funktionsaufruf am Stack abgelegt werden. Der Vereinbarungsteil
der Funktionen f1 und f2 ist gleich. (Die Bezeichner der Variablen müssten nicht gleich sein.) Damit kann demonstriert werden, wie die Variablen beim Aufruf von f1 am Stack erzeugt werden, der Stack dann wieder freigegeben wird, um beim Aufruf von f2 wieder in der gleichen Weise aufgebaut zu werden.

Programmausgabe LCC ohne Debug Support:
Debug-Informationen deaktivieren: Project -> Configuration -> Compiler -> Generate Debug Info - Checkbox deaktivieren.

main = 004012FC
 f1 = 0040123C
 f2 = 00401288
 f1: &n1 = 0012FF6C, n1 = 1
 f1: &n2 = 0012FF68, n2 = 2
 f2: &n1 = 0012FF6C, n1 = 1  <- noch vom f1-Aufruf im Speicher
 f2: &n2 = 0012FF68, n2 = 2  <- noch vom f1-Aufruf im Speicher
 f2: &n1 = 0012FF6C, n1 = 3
 f2: &n2 = 0012FF68, n2 = 4
In der Debug-Konfiguration wird Maschinen-Code erzeugt, der die Variablen mit einem Wert inititialisiert. Aber man sieht auch hier, dass f1 und
f2 den Stack in der selben Weise aufbauen und sich für die Variablen der Funktion f2 wieder gleiche Adressen ergeben.

Programmausgabe LCC:
main = 0040131A
f1 = 0040123C
f2 = 00401297
f1: &n1 = 0012FF6C, n1 =      1
f1: &n2 = 0012FF68, n2 =      2
f2: &n1 = 0012FF6C, n1 = -370086
f2: &n2 = 0012FF68, n2 = -370086
f2: &n1 = 0012FF6C, n1 =      3
f2: &n2 = 0012FF68, n2 =      4

Wenn Sie das Programm mit dem Debugger ausführen, können sie mit F8 (verzweigt in Funktion) und/oder F4 (verzweigt nicht in Funktion)
die Programmzeilen schrittweise abarbeiten. Zusätzlich kann angezeigt werden, was in der CPU passiert, dieses Fenster zeigt auch den Stack (allerdings mit relativen Adressen) an. Und sie können den Maschinen Code (Debug -> Mashine Instructions) anzeigen. Dann wird der Maschinencode schrittweise ausgeführt. Beachten Sie auch das Kontextmenü, das mit der rechten Maustaste erzeugt wird.

Programmausgabe Visual C++ (Release Konfiguration):

main = 004010B0
f1 = 00401000
f2 = 00401040
f1: &n1 = 0012FF7C, n1 = 1
f1: &n2 = 0012FF78, n2 = 2
f2: &n1 = 0012FF7C, n1 = 1
f2: &n2 = 0012FF78, n2 = 2
f2: &n1 = 0012FF7C, n1 = 3
f2: &n2 = 0012FF78, n2 = 4

Programmausgabe Visual C++ (Debug Konfiguration):

main = 00401005
  f1 = 0040100A
  f2 = 0040100F
f1: &n1 = 0012FF28, n1 =      1
f1: &n2 = 0012FF24, n2 =      2
f2: &n1 = 0012FF28, n1 = -858993460
f2: &n2 = 0012FF24, n2 = -858993460
f2: &n1 = 0012FF28, n1 =      3
f2: &n2 = 0012FF24, n2 =      4

Anmerkung: -858992460 = 0xCCCCCCCC

Debugger: Setzen Sie auf die erste Zeile in main einen Haltepunkt (F9) und starten Sie das Programm mit F5.
Über Ansicht -> Debug-Fenster -> ... können Sie weitere Fenster einblenden.
So schaut es dann z.B. aus:

Der nächste Programmschritt ist die printf-Funktion in der Funktion f1.

Das Disassembler Fenster zeigt den Maschinencode mit den optional angezeigten Quellcode-Zeilen an:

8:    void f1(void)
9:    {
00401030   push        ebp
00401031   mov         ebp,esp
00401033   sub         esp,50h
00401036   push        ebx
00401037   push        esi
00401038   push        edi
00401039   lea         edi,[ebp-50h]
0040103C   mov         ecx,14h
00401041   mov         eax,0CCCCCCCCh
00401046   rep stos    dword ptr [edi]
10:       int n1;
11:       int n2;
12:       int *pn1 = &n1;
00401048   lea         eax,[ebp-4]
0040104B   mov         dword ptr [ebp-0Ch],eax
13:       int *pn2 = &n2;
0040104E   lea         ecx,[ebp-8]
00401051   mov         dword ptr [ebp-10h],ecx
14:
15:       n1 = 1;
00401054   mov         dword ptr [ebp-4],1
16:       n2 = 2;
0040105B   mov         dword ptr [ebp-8],2
17:
18:       printf("f1: &n1 = %p, n1 = %6i\n", &n1, n1);
00401062   mov         edx,dword ptr [ebp-4]
00401065   push        edx
00401066   lea         eax,[ebp-4]
00401069   push        eax
0040106A   push        offset string "f1: &n1 = %p, n1 = %6i\n" (00420038)
0040106F   call        printf (004011f0)
00401074   add         esp,0Ch
19:       printf("f1: &n2 = %p, n2 = %6i\n", &n2, n2);
00401077   mov         ecx,dword ptr [ebp-8]
0040107A   push        ecx
0040107B   lea         edx,[ebp-8]
0040107E   push        edx
0040107F   push        offset string "f1: &n2 = %p, n2 = %6i\n" (0042001c)
00401084   call        printf (004011f0)
00401089   add         esp,0Ch
20:
21:   }
0040108C   pop         edi
0040108D   pop         esi
0040108E   pop         ebx
0040108F   add         esp,50h
00401092   cmp         ebp,esp
00401094   call        __chkesp (00401270)
00401099   mov         esp,ebp
0040109B   pop         ebp
0040109C   ret

Einen Überblick über den Pentium Befehlssatz finden Sie z.B. als Vorlesungsskriptum (PDF): Lecture 7 (Vorlesung 7) auf der Web-Seite des Instituts für Technische Informatik an der Humbolt Universität Berlin.