Arbeitsblatt #4

Werbung
Arbeitsblatt #4
Einführung in die Systemprogrammierung SS 2013
24. Mai 2013
Abgabedatum: 17:00, 03.06.2013
In dieser Übung beschäftigen wir uns mit den Grundlagen der C-Programmierung. Dazu verwenden
wir zwei Operationen, die durch die Präprozessoranweisung
#i n c l u d e <s t d i o . h>
importiert werden können:
• printf(), das sich wie in Übung #01 verhält und das wir hier zum Ausgeben von Zeichenketten
und Zahlen verwenden. printf nimmt einen oder mehrere Parameter, wobei der erste Parameter immer eine Zeichenkette sein muß. Diese erste Zeichenkette kann Formatierungszeichen
beinhalten, wie z.B. %d oder %s. Mit Ausnahme des Formatierungszeichens %%, das immer ein
Prozentsymbol ausgibt, bezieht sich das nte Formatierungssymbol der Zeichenkette immer auf
den (n + 1)ten Parameter und fügt diesen an der betreffenden Stelle in der Zeichenkette ein.
Dabei gilt:
– %d erwartet als zugehörigen Parameter eine int-Zahl und fügt diese als Dezimalzahl ein.
– %s erwartet als zugehörigen Parameter eine Zeichenkette (ein char-array) und fügt diese
direkt ein. printf ignoriert Formatierungszeichen in dieser eingefügten Zeichenkette.
Beispiel: printf("%d%% von %d sind %d\n", 20, 45, 9); gibt 20% von 45 sind 9“ aus,
”
gefolgt von einem Zeilenumbruch.
• scanf(), das das Gegenstück zu printf zum Einlesen von Zahlen ist. Wir benötigen es hier nur
in der Form scanf("%d", &i), in der es eine einzelne Zahl einliest und in die int-Variable ‘i’
schreibt (die genaue Bedeutung des &-Präfixoperators behandeln wir in der nächsten Vorlesung).
1
Zahlen raten (2 Punkte)
Verwenden Sie die Kontrollflußstrukturen aus der Vorlesung, um ein Zahlenratespiel zu implementieren. Ihr Programm soll sich eine Zahl zwischen 0 und 1000 (inklusive) merken und den Spieler diese
raten lassen. Der genaue Prozeß ist wie folgt:
• Das Programm entscheidet sich für eine Zahl. Es ist ausreichend, wenn Sie eine bestimmte Zahl
fest einkodieren, aber sie können die Zahl auch von einem Pseudozufallszahlengenerator erzeugen
lassen.
• Das Programm liest eine ganze Zahl vom Spieler ein.
• Wenn die Zahl falsch war, gibt das Programm aus, ob die Zahl zu hoch oder zu niedrig geraten
wurde, und wiederholt den letzten Schritt.
• Wenn die Zahl mit der gemerkten Zahl übereinstimmt, druckt das Programm (a) eine Erfolgsmeldung und (b) die Anzahl der vom Spieler benötigten Schritte aus und beendet sich.
1
Optional: Sie können optional den C89-Pseudozufallszahlengenerator verwenden. Verwenden Sie
dazu die Präprozessoranweisung
#i n c l u d e <time . h>
#i n c l u d e < s t d l i b . h>
und den Befehl
s ran d ( time ( 0 ) ) ; // P s e u d o z u f a l l s z a h l e n g e n e r a t o r mit U h r z e i t i n i t i a l i s i e r e n
zu Beginn Ihres Programmes. Danach können Sie die Funktion rand() verwenden, die eine nichtnegative int-Pseudozufallszahl erzeugt. Den Maximalwert dieser Zahl können Sie durch den Divisionsrestoperator % erzwingen– die entsprechende Zufallsverteilung ist nicht komplett gleichmäßig, aber das ist
für unsere Zwecke akzeptabel.
2
Printf (1 Punkt)
Das Verhalten von printf ist in einigen Fällen nicht definiert. Untersuchen Sie, wie Ihre Implementierung der Sprache mit diesen Fällen umgeht:
a. eine Zeichenkette als Parameter für ein %d-Formatierungssymbol verwenden
b. eine int-Zahl als Parameter für ein %s-Formatierungssymbol verwenden
3
Abkürzungen in der Auswertung (1 Punkt)
Die booleschen Logik-Operatoren (&&) und (||) berechnen einen C-Wahrheitswert (unwahr (0) oder
wahr (nicht-0)) abhängig von den beiden Wahrheitswerten ihrer Argumente (links und rechts): für &&
müssen beide Argumente wahr sein, für || muß mindestens einer wahr sein.
Einige Programmiersprachen verwenden eine Optimierung für diese Operatoren, die deren logische
Eigenschaften ausnutzt. Betrachten Sie den folgenden Ausdruck:
0 && 17 ∗ 304 / 11 + 9 > 470
Wir können den Wahrheitswert des Gesamtausdruckes bestimmen, ohne die komplexe Berechnung 17
* 304 / 11 + 9 > 470 durchführen zu müssen, da wir wissen, daß a && b nur wahr sein kann, wenn
sowohl a und b wahr sind– aber da a bereits unwahr (0) ist, muß der gesamte Ausdruck notwendigerweise unwahr sein.
Wenn die Programmiersprache diese Einsicht verwendet, um einen der Werte (normalerweise den
rechten) nicht auszuwerten, bezeichnet man dies als Kurzschlußauswertung (short-circuit evaluation).
Im obigen Fall würde die Programmiersprache also nur 0 auswerten und die Berechnung 17 * 304 /
11 + 9 > 470 komplett ignorieren.
Viele, aber nicht alle Programmiersprachen verwenden dieses Verfahren. Finden Sie heraus, ob
C Kurzschlußauswertung verwendet, entweder durch ein geeignetes Testprogramm (dies bitte in der
Lösung mit angeben!) oder durch Bezug auf die Sprachspezifikation. Überprüfen Sie dies in beide
Richtungen, also sowohl für den Fall, daß der linke Parameter das Ergebnis der Berechnung vorherbestimmt, als auch für den Fall, daß der rechte Parameter dies tut!
4
Hilfsfunktionen für die Taschenrechnersprache (2 Punkte)
Als Vorbereitung für die letzte Teilaufgabe definieren wir Hilfsfunktionen. Hier ist ein Beispiel für eine
Hilfsfunktion verdoppeln:
2
#i n c l u d e <s t d i o . h>
// D e f i n i t i o n d e r Funktion ‘ v e r d o p p e l n ’ .
// D i e s e Funktion nimmt genau e i n e n Parameter , ‘ z a h l ’
unsigned verdoppeln ( unsigned zahl )
{
// Im I n n e r e n d e r Funktion koennen w i r a u f den Parameter
// ‘ z a h l ’ z u g r e i f e n , a l s ob e r e i n e l o k a l e V a r i a b l e waere .
// Mit ‘ r e t u r n x ’ koennen w i r e i n b e l i e b i g e s ‘ x ’ ( s o l a n g e e s e i n e
// n i c h t v o r z e i c h e n b e h a f t e t e Zahl i s t ) a l s E r g e b n i s z u r u e c k l i e f e r n :
return zahl ∗ 2;
}
// D e f i n i e r t e Funktionen koennen von d a r u n t e r l i e g e n d e n Funktionen
// verwendet werden .
main ( )
{
// ‘ main ’ kann a u f ‘ v e r d o p p e l n ’ z u g r e i f e n :
p r i n t f ( ”%d\n” , v e r d o p p e l n ( 7 ) ) ;
}
Eine Hilfsfunktion mit zwei Parametern kann wie im folgenden Beispiel definiert werden:
unsigned m u l t i p l i z i e r e n ( unsigned zahl1 , unsigned zahl2 )
{
int zwischenergebnis = zahl1 ∗ zahl2 ;
p r i n t f ( ” Berechnung e r g a b %d\n” , z w i s c h e n e r g e b n i s ) ;
return zwischenergebnis ;
}
Betrachten Sie die vereinfachte Taschenrechnersprache aus dem Appendix. Mit einem C-Übersetzer,
der int als 32-Bit-Wert interpretiert, können wir jeden Taschenrechnerbefehl in einem unsigned int
ablegen. Bauen Sie Hilfsfunktionen, um Eigenschaften dieser Befehle auszulesen:
• Den Operationscode (die höchstwertigen 6 Bits). Der Operationscode sollte als Zahl von 0x0 bis
0x3f zurückgeliefert werden.
• Den Funktionscode (die niedrigstwertigen 6 Bits).
• Den Direktwert (die niedrigstwertigen 16 Bits). Der Direktwert ist in der Taschenrechnersprache,
anders als in MIPS, nicht vorzeichenbehaftet.
• Das kte Register. Schreiben Sie eine einzige Funktion, mit der die bis zu drei in einem Befehlswort
kodierten Registernummern ausgelesen werden können.
5
Interpreter für die Taschenrechnersprache (4 Punkte)
Verwenden Sie die Funktionen aus der vorherigen Teilaufgabe, um die Taschenrechnersprache zu implementieren. Testen Sie Ihre Implementierung mit dem folgenden Taschenrechnerprogramm:
3
int befehle nr = 8;
unsigned b e f e h l e [ 1 0 ] = {
0 xfc200000 ,
0 x34600064 ,
0 x000110c0 ,
0 x00621022 ,
0 x00240820 ,
0 x70210800 ,
0 xfc200001 ,
0 xfc400001 ,
};
a. Schreiben Sie ein Programm, das über alle Befehle in befehle iteriert. Die Iterationsvariable ist
also Ihr Programmzähler.
b. Dekodieren Sie die Befehle teilweise, durch Analyse des Operationscodes und, falls nötig, Funktionscodes. Wenn ein Befehl ungültig ist, geben Sie eine Begründung für die Ungültigkeit aus
und überspringen Sie den Befehl.
c. Führen Sie die Befehle nacheinander aus. Gehen Sie davon aus, daß alle vier gültigen Register
auf 0 initialisiert sind. Geben Sie eine Fehlermeldung aus, falls eine ungültige Registernummer
verwendet wird; verwenden Sie in diesem Fall nur die Inhalte der zwei niederwertigen Bits der angegebenen Registernummer als tatsächliche Registernummer. Ein versuchter Zugriff auf Register
9 würde also z.B. in einen Zugriff auf Register 1 umgewandelt (nach Fehlermeldung).
Sie können nach Bedarf die Funktionen aus Teilaufgabe 3 verwenden und modifizieren, oder neue
Funktionen schreiben.
A
Taschenrechner-Befehle
Beachten Sie, daß mit der Notation xi hier ein Zugriff auf das i-te Bit von x gemeint ist.
Berechnung Assemblersprache
Bitmuster / Instruktion
⇒ $z
eingabe $z
111111 000z1 z0 00000 00000 00000 000000
$z ⇒
ausgabe $z
111111 000z1 z0 00000 00000 00000 000001
$z := $x + $y add $z, $x, $y
000000 000x1 x0 000y1 y0 000z1 z0 00000 100000
$z := $x − $y sub $z, $x, $y
000000 000x1 x0 000y1 y0 000z1 z0 00000 100010
$z := $x × $y mul $z, $x, $y
011100 000x1 x0 000y1 y0 000z1 z0 00000 000000
$z := $x d sll $z, $x, d
000000 00000 000x1 x0 000z1 z0 000d1 d0 000000
$z := $x d srl $z, $x, d
000000 00000 000x1 x0 000z1 z0 000d1 d0 000100
$z := v
li $z, v
001101 000z1 z0 00000
v15 v14 . . . v1 v0
4
Herunterladen