1 pulsierender Speicher

Ähnliche Dokumente
Globale Variablen Diverses. Globale Variablen. Globale Variablen

EWS, WS 2016/17 Pfahler I-1

Zeiger. C-Kurs 2012, 2. Vorlesung. Tino Kutschbach 10.

Vorlesung Programmieren

Funktionen. mehrfach benötigte Programmteile nur einmal zu schreiben und mehrfach aufzurufen

C# - Einführung in die Programmiersprache Methoden. Leibniz Universität IT Services

Inhalt. 4.9 Typen, Variable und Konstante

Kapitel 3. Programmierkurs. Arten von Anweisungen. 3.1 Was sind Anweisungen?

Java Einführung Methoden. Kapitel 6

Präzedenz von Operatoren

2. Unterprogramme und Methoden

2. Programmierung in C

Typ : void* aktuelle Parameter Pointer von beliebigem Typ

Einführung in die Programmierung Wintersemester 2016/17

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

Praxis der Programmierung

Imperative Programmierung in Java: Kontrollfluß II

C++ - Objektorientierte Programmierung Konstante und statische Elemente

Einführung in die Programmierung

(Ausnahmebehandlung)

Grundlagen der OO- Programmierung in C#

7 Laufzeit-Speicherverwaltung

7 Funktionen. 7.1 Definition. Prototyp-Syntax: {Speicherklasse} {Typ} Name ({formale Parameter});

Programmierung in C: Vermischtes (Teil 1)

C++ - Einführung in die Programmiersprache Funktionen. Leibniz Universität IT Services Anja Aue

C++ - Objektorientierte Programmierung Konstruktoren und Destruktoren

6 Speicherorganisation

2. Programmierung in C

C++ - Einführung in die Programmiersprache Zeiger, Referenzen und Strukturen. Leibniz Universität IT Services Anja Aue

6. Funktionen, Parameterübergabe

CS1005 Objektorientierte Programmierung

Java Methoden. Informatik 1 für Nebenfachstudierende Grundmodul. Kai-Steffen Hielscher Folienversion: 1. Februar 2017

Funktionen in JavaScript

6. Unterprogramme 6-1

Einführung in die Programmierung II. 4. Funktionen

Rekursive Funktionen (1)

Einstieg in die Informatik mit Java

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 1. Teil

Rekursive Funktionen (1)

Speicherklassen (1) Lokale Variablen

Kapitel 4. Kontrollstrukturen

ÜBUNGS-BLOCK 7 LÖSUNGEN

Funktionen in JavaScript

Prozeduren und Funktionen

Implementieren von Klassen

Ein paar Kleinigkeiten zum Einstieg

Einstieg in die Informatik mit Java

C++ - Variablen: Gültigkeit - Sichtbarkeit

6 Speicherorganisation

Übung zur Vorlesung Wissenschaftliches Rechnen Sommersemester 2012 Auffrischung zur Programmierung in C++, 1. Teil

Offenbar hängt das Ergebnis nur von der Summe der beiden Argumente ab...

Software Entwicklung 1. Rekursion. Beispiel: Fibonacci-Folge I. Motivation. Annette Bieniusa / Arnd Poetzsch-Heffter

Programmieren I. Kapitel 12. Referenzen

Grundlagen der Objektorientierten Programmierung - Methoden -

Grundlagen der Programmierung Prof. H. Mössenböck. 6. Methoden

Algorithmen und Datenstrukturen

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

2. Programmierung in C

Algorithmen implementieren. Implementieren von Algorithmen

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java

Klausur Kompaktkurs Einführung in die Programmierung Dr. T. Weinzierl & M. Sedlacek 25. März 2011

Heap vs. Stack vs. statisch. 6 Speicherorganisation. Beispiel Statische Variablen. Statische Variablen

Kapitel 4. Kontrollstrukturen

Praxis der Programmierung

Methoden. Gerd Bohlender. Einstieg in die Informatik mit Java, Vorlesung vom

C++ - Funktionen und mehr -

Programmierung mit C Zeiger

Kapitel 4: Anweisungen und Kontrollstrukturen

Programmierung mit C Funktionen

C++ Teil 2. Sven Groß. 16. Apr IGPM, RWTH Aachen. Sven Groß (IGPM, RWTH Aachen) C++ Teil Apr / 22

JAVA - Methoden - Rekursion

Einführung in den Einsatz von Objekt-Orientierung mit C++ I

Die Sprache C# Datentypen, Speicherverwaltung Grundelemente der Sprache. Dr. Beatrice Amrhein

Variablen in Java. Martin Wirsing. in Zusammenarbeit mit Michael Barth, Philipp Meier und Gefei Zhang. Ziele

Mussten wir eine ganze Reihe solcher Zahlen speichern, haben wir das eindimensionale Array verwendet.

Einleitung Grundlagen Erweiterte Grundlagen Zusammenfassung Literatur. C: Funktionen. Philip Gawehn

Algorithmen und Datenstrukturen

2. Programmierung in C

DieÜbersetzung funktionaler Programmiersprachen

Kontrollfluss. man Verzweigungen und Sprünge. o bisher linear (von oben nach unten) o Für interessante Programme braucht

Die Sprache C# Datentypen, Speicherverwaltung Grundelemente der Sprache. Dr. Beatrice Amrhein

Einführung in die Programmiersprache C

41.8 LUA-Grundlagen - Tabelle, if und Albernheit

Grundelemente objektorientierter Sprachen (1)

Programmiersprachen und ihre Übersetzer

Verwendung Vereinbarung Wert einer Funktion Aufruf einer Funktion Parameter Rekursion. Programmieren in C

Methoden und Wrapperklassen

JavaScript. Dies ist normales HTML. Hallo Welt! Dies ist JavaScript. Wieder normales HTML.

Programmiersprache 1 (C++) Prof. Dr. Stefan Enderle NTA Isny

Einstieg in die Informatik mit Java

Einstieg in die Informatik mit Java

zu große Programme (Bildschirmseite!) zerlegen in (weitgehend) unabhängige Einheiten: Unterprogramme

Transkript:

1 pulsierender Speicher 1.1 Aufgabentyp Gegeben sei das folgende C-Programm: [...] (a) Geben Sie den Gültigkeitsbereich jedes Objektes des Programms an. (b) Stellen Sie die Rechnung des Programms für die Eingabe [...] als pulsierenden Speicher dar, wobei die aktuelle Situation bei jedem Passieren der Marken [...] gezeigt werden soll. Führen Sie des Weiteren auch ein Rücksprungmarkenprotokoll. 1.2 Überblick 1.2.1 Gültigkeitsbereich Objekte = Funktionen (inklusive der main-funktion) und alle Variablen (lokale, globale Variablen sowie formale Parameter) Funktionen ab der Zeile ihrer Deklaration (dies entspricht dem ersten Auftreten des Funktionskopfes, die eigentlich Implementierung kann auch weiter unten erfolgen) bis zur letzten Zeile des Programmes Variablen global ab der Zeile ihrer Deklaration bis zum Ende des Programmes (sie werden jedoch eventuell innerhalb von Funktionen durch lokale Variablen gleichen Namens überdeckt) lokal und formale Parameter nur in der jeweiligen Funktion gültig (von der Zeile der Deklaration bis zur schließenden geschweiften Klammer der Funktion) 1.2.2 pulsierender Speicher Der pulsierende Speicher besteht aus 3 Spalten: dem Haltepunkt, dem Rücksprungmarkenkeller und der Umgebung. Haltepunkt Zum Aufstellen des pulsierenden Speichers wird die Abarbeitung des Programms Schritt für Schritt verfolgt. Immer wenn die Ausführung des Programmes ein Label erreicht, wird eine neue Zeile im pulsierenden Speicher angelegt. Der Haltepunkt entspricht dem Namen des Labels. Rücksprungmarkenkeller gibt immer den derzeitigen Aufrufstack der Funktionen an. Jeder Aufruf legt eine Rücksprungmarke auf den Keller. Umgebung bildet den Speicher ab, d.h. an welchen Adressen welche Werte vorhanden sind. Namen werden immer nur für aktuell sichtbare Variablen geschrieben, die Werte müssen dagegen für alle belegten Speicherplätze angegeben werden. Die Umgebung wächst mit jedem Funktionsaufruf und schrumpft nach Verlassen einer Funktion, da sich die Anzahl benötigter Speicherplätze verändert (daher der Name pulsierender Speicher). Wird ein neuer Speicherplatz benötigt, wird immer die erste freie Adresse zugewiesen. 1

1.3 Grundlagen 1.3.1 Deklaration einer Variablen Es gibt drei Arten von Variablen: lokale, globale und formale Parameter. Der Speicher der globalen Variablen wird noch vor dem Aufruf der Mainfunktion reserviert und erst beim Beenden des Programmes wieder freigegeben. Daher sind diese Speicherplätze über die gesamte Programmlaufzeit belegt und werden in der Umgebung durch einen senkrechten Strich abgetrennt von lokalen Variablen getrennt. Speicher für lokale Variablen wird bei der ersten Deklaration reserviert und beim Verlassen des zugehörigen Blockes wieder freigegeben. Eine lokale Variable in einer Funktion wird also durch ihre jeweilige Deklaration angelegt und beim Verlassen der Funktion wieder entfernt. Die formalen Parameter werden beim Aufruf einer Funktion angelegt und beim Verlassen wieder gelöscht - dies ist auch in den zwei nachfolgenden Abschnitten näher beschrieben. 1.3.2 Aufruf einer Funktion Wird eine Funktion per func(param1,param2,...); aufgerufen, laufen folgende Schritte ab: 1. Sichern der aktuellen Rücksprungmarke, welche direkt hinter dem Funktionsaufruf steht 2. Anlegen der formalen Parameter auf dem Stack 3. Übergabe der Werte der aktuellen Parameter an die formalen Parameter, d.h. Zuweisung der Werte des Funktionsaufrufs zu den entsprechenden Paramtervariablen 4. Sprung zur Funktion Im pulsierenden Speicher werden der Stack für die Rücksprungmarken (1.) und der Stack für die Variablen (2.) getrennt geführt. Ersterer wird im Rücksprungmarkenprotokoll festgehalten (ähnlich wie beim Rücksprungalgorithmus) und letzterer in der Umgebung. 1.3.3 Beenden einer Funktion Eine Funktion wird bei einer return-anweisung oder an der letzten Zeile einer void-funktion beendet und es muss zum Aufrufer der Funktion zurückgesprungen werden. Im wesentlichen wird beim Rücksprung aus einer Funktion der Ausgangszustand wiederhergestellt: 1. Freigeben des Stacks der Funktionsvariablen (Parameter, lokale Variablen) 2. Freigeben der Rücksprungmarke 3. Rücksprung Dies hat für den pulsierenden Speicher zur Folge, dass beim Rücksprung aus einer Funktion die Umgebung meist kleiner wird und sich der Rücksprungmarkenkeller verkleinert. 1.4 Erklärung am Beispiel 1.4.1 Aufgabe Gegeben sei das folgende C Programm: 2

1 #include <s t d i o. h> 2 int a, b ; 3 4 void g ( int x, int y ) { 5 / l a b e l 1 / 6 i f ( y > 1) { 7 g ( x, y 1 ) ; / $1 / 8 x = x x ; / l a b e l 2 / 9 } 10 else x = x + 1 ; 11 } 12 13 void f ( int x, int y, int z ) { 14 / l a b e l 3 / 15 i f ( x > 0) { 16 f ( x 2, y, z ) ; / $2 / / l a b e l 4 / 17 g ( z, x ) ; / $3 / / l a b e l 5 / 18 } 19 else z = y ; 20 } 21 22 int main ( ) { 23 int c ; 24 p r i n t f ( "\n Zahl a : " ) ; 25 s c a n f ( "%d", &a ) ; 26 p r i n t f ( "\n Zahl b : " ) ; 27 s c a n f ( "%d", &b ) ; / l a b e l 6 / 28 f ( a, &b, &c ) ; / $4 / / l a b e l 7 / 29 p r i n t f ( "\n Das Ergebnis l a u t e t : %d\n\n", c ) ; 30 return 0 ; 31 } (a) Geben Sie den Gültigkeitsbereich jedes Objektes des Programms an. (b) Stellen Sie die Rechnung des Programms für die Eingabe a = 2 und b = 2 als pulsierenden Speicher dar, wobei die aktuelle Situation bei jedem Passieren der Marken label1 bis label7 gezeigt werden soll. Führen Sie des Weiteren auch ein Rücksprungmarkenprotokoll. Beachten Sie: $1 bis $4 sind die bereits festgelegten Rücksprungmarken. 1.4.2 Gültigkeit der Objekte Mit Objekte sind die Funktionen und alle Variablen in dem gegebenen Quelltext gemeint. Eine Funktion oder Variable ist in einer Zeile gültig, wenn sie ohne Compilerfehler aufgerufen werden kann. Damit ergibt sich: Funktionen g: Zeile 4 31 (g könnte ab Zeile 4 hinter der geschweiften Klammer bis Zeile 31 aufgerufen werden) f: Zeile 13 31 3

main: Zeile 22 31 Variablen global: a,b: Zeile 2 31 in g: x,y: 4 11 in f: x,y,z: 13 20 in main: c: 23 31 Hinweis: Nicht immer müssen globale Variablen überall sichtbar sein! In C überschreiben lokale Variablen die Sichtbarkeit der globalen. Hätte also die Funktion f als Parameter noch a, wäre das globale a nur von 2 3 und 12 31 sichtbar. (Und das lokale a entsprechend Zeile 4 11). 1.4.3 pulsierender Speicher Die Protokollierung beginnt mit folgender Tabelle: Das Programm wird nun (im Kopf) bis zum ersten Haltepunkt ausgeführt. Haltepunkte werden durch einen Kommentar der Form /* label<nr> */ gekennzeichnet. Der Code wird stets bis direkt vor dem Haltepunkt ausgeführt. Befindet sich also der Haltepunkt in einer Zeile hinter einem Funktionsaufruf, wird zuerst diese Funktion abgearbeitet. Das Programm startet in der Funktion main, also in Zeile 22. Die Zeile 23 legt eine lokale Variable an, danach folgt die Ein- und Ausgabe, bis wir in Zeile 27 auf label6 stoßen. Es befinden sich jetzt also die 2 globalen Variablen a und b sowie die lokale Variable c in der Umgebung. Der Markenkeller ist leer, da wir uns noch in der Hauptfunktion befinden. Für a und b wurden laut Aufgabenstellungen Werte festgelegt (durch die Nutzereingabe). Damit ergibt sich (in der Umgebung stehen oben die Variablennamen sofern sichtbar und unten die entsprechenden Werte): In der nächsten Zeile (28) findet ein Funktionsaufruf statt. Dies bedeutet: 1. die Rücksprungmarke muss gesichert werden, d.h. die festgelegte Marke 4 wird auf den Keller gelegt 2. die neuen Parameter müssen angelegt und initialisiert werden. Die Funktion f hat den Wertparameter x und die zwei Referenzparameter y und z. Bei ersterem wird der Wert einfach kopiert, d.h. der Wert von a wird in den Speicher für das lokale x kopiert. Damit hat x den Wert 2. Der erste Referenzparameter y bekommt die Adresse von b zugewiesen; dies wird im pulsierenden Speicher mit dem #-Symbol verdeutlicht. Da b in der Umgebung an Speicherplatz 2 abgelegt ist, hat y damit den Wert #2. Der Speicherplatz 2 ist nun also durch b und *y ansprechbar. Ähnliches gilt für z: Der Speicher mit der Umgebungsnummer 3 ist über *z erreichbar. c ist eine lokale Variable der Mainfunktion, hier also nicht mehr sichtbar. Damit ergibt sich folgendes: 4

2 #2 #3 Da x in Zeile 15 tatsächlich größer 0 ist, wird als nächstes die Funktion f noch einmal rekursiv aufgerufen. Im Speicherbelegungsprotokoll erhalten nur die Variablen einen Namen, die auch momentan sichtbar sind. Deshalb ergibt sich: 2 #2 #3 label3 2:4 a b,*y *z x y z 2 #2 #3 0 #2 #3 Da x nun nicht mehr größer als 0 ist, wird der else-zweig in Zeile 19 ausgeführt, sodass von Speicherplatz 2 der Inhalt nach Speicherplatz 3 kopiert wird - Speicher 3 erhält somit als Wert die 2. Da die nun folgende Zeile 20 die letzte in der Funktion ist, erfolgt der Rücksprung. Die lokalen Variablen der aktuellen Instanz von f werden also gelöscht und die oberste Rücksprungmarke (2) wird dem Markenkeller entnommen um an die Aufrufstelle zurückzukehren. Ein Blick in den Quelltext ergibt, dass diese Stelle vor label4 war. Es empfiehlt sich im Markenkeller von unten nach oben nach dem alten Kontext zu suchen, so dass man die Variablenbezeichnung von dort abschreiben kann. Die Werte sollte man allerdings von der letzten Zeile übernehmen, da diese sich vermutlich geändert haben. Hier werden wir in Zeile 2 der Tabelle fündig und es ergibt sich: 2 #2 #3 label3 2:4 a b,*y *z x y z 2 #2 #3 0 #2 #3 label4 4 a b,*y *z x y z 2 2 2 2 #2 #3 Die nächsten Aufrufe verlaufen analog, sodass sich insgesamt folgendes Ablaufprotokoll ergibt: 5

2 #2 #3 label3 2:4 a b,*y *z x y z 2 #2 #3 0 #2 #3 label4 4 a b,*y *z x y z 2 2 2 2 #2 #3 label1 3:4 a b *x x y 2 2 2 2 #2 #3 #3 2 label1 1:3:4 a b *x x y 2 2 2 2 #2 #3 #3 2 #3 1 label2 3:4 a b *x x y 2 2 9 2 #2 #3 #3 2 label5 4 a b,*y *z x y z 2 2 9 2 #2 #3 label7 - a b c 2 2 9 1.5 Anmerkungen 1.5.1 Zeiger Die Bezeichnung der Speicherstelle auf die gezeigt wird (z.b. mit *y) ist in der Prüfung nicht gefordert. Je nach Geschmack kann dies also auch weggelassen werden. Dies spart zwar Schreibarbeit, aber bei einer entsprechenden Rechenoperation muss dann im Kopf dereferenziert werden. 1.5.2 Sichtbarkeit In C gilt der so genannte static scope. Dabei wird immer die Variable genutzt, deren Deklaration am nächsten steht. Eine lokale Variable oder ein Parameter überschreibt also eine globale Variable, d.h. die globale Variable wird in diesem Fall überdeckt und ist nicht mehr sichtbar. 1 int x, y ; 2... 3 void f ( int x ) { 4 int y = x ; 5 } 6... Die globale Variable x ist in diesem Fall also von Zeile 1-2 sowie ab Zeile 6 sichtbar. In der Funktion f wird das globale x von dem lokalen x überdeckt, d.h. der formale Parameter x von f ist von Zeile 3 bis 5 sichtbar. Analoges gilt für y: Das globale y ist von Zeile 1 bis 3 sowie ab Zeile 6 sichtbar. Das lokale y wird dagegen von Zeile 4 bis 5 genutzt. Der Vorteil dieses Verfahrens ist, dass die Sichtbarkeit schon zur Compilierzeit klar ist, wie folgendes Beispiel klar macht: 1 int x = 0 ; 6

2 int f ( ) { return x ; } 3 int g ( ) { int x = 1 ; return f ( ) ; } Wird nun die Funktion g aufgerufen, liefert sie den Wert 0 zurück (da f immer das globale x verwendet). Das Gegenteil von static scope ist dynamic scope. Dabei wird immer der Wert der Variablen benutzt, der zuletzt beim Aufruf der aktuellen Funktion gültig war. Für das Beispiel oben würde damit die Funktion g den Wert 1 zurückliefern. Dieses Verfahren ist besser für der Interpretierung von Sprachen geeignet und wird da meist in Skriptsprachen (wie z.b. Perl) eingesetzt. 7