Skip to content
Snippets Groups Projects
Commit daaabdb1 authored by Peter Gerwinski's avatar Peter Gerwinski
Browse files

Musterlösung 22.10.2018

parent 1faebc1e
No related branches found
No related tags found
No related merge requests found
File added
% hp-musterloesung-20181015.pdf - Solutions to the Exercises on Low-Level Programming / Applied Computer Sciences
% Copyright (C) 2013, 2015, 2016, 2017, 2018 Peter Gerwinski
%
% This document is free software: you can redistribute it and/or
% modify it either under the terms of the Creative Commons
% Attribution-ShareAlike 3.0 License, or under the terms of the
% GNU General Public License as published by the Free Software
% Foundation, either version 3 of the License, or (at your option)
% any later version.
%
% This document is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this document. If not, see <http://www.gnu.org/licenses/>.
%
% You should have received a copy of the Creative Commons
% Attribution-ShareAlike 3.0 Unported License along with this
% document. If not, see <http://creativecommons.org/licenses/>.
% README: ROT13-Verschlüsselung, Programm analysieren, Kalender-Berechnung
\documentclass[a4paper]{article}
\usepackage{pgscript}
\begin{document}
\section*{Hardwarenahe Programmierung\\
Musterlösung zu den Übungsaufgaben -- 22.\ Oktober 2018}
\exercise{ROT13-Verschlüsselung}
Schreiben Sie ein C-Programm, das einen Text entgegennimmt,
jeden Buchstaben von A bis Z zyklisch um 13 Stellen verschiebt
und den auf diese Weise "`verschlüsselten"' Text wieder ausgibt.
Umlaute, Ziffern, Satz- und Sonderzeichen sollen nicht verändert werden.
\begin{tabular}{ccc}
A & $\longrightarrow$ & N \\
B & $\longrightarrow$ & O \\
& \dots & \\
M & $\longrightarrow$ & Z \\
N & $\longrightarrow$ & A \\
& \dots & \\
Y & $\longrightarrow$ & L \\
Z & $\longrightarrow$ & M
\end{tabular}
\qquad
\begin{tabular}{ccc}
a & $\longrightarrow$ & n \\
b & $\longrightarrow$ & o \\
& \dots & \\
m & $\longrightarrow$ & z \\
n & $\longrightarrow$ & a \\
& \dots & \\
y & $\longrightarrow$ & l \\
z & $\longrightarrow$ & m
\end{tabular}
Beispiel: Aus "`Apfelkuchen"' wird "`Ncsryxhpura"'.
\solution
Datei \gitfile{hp}{20181022}{loesung-1.c}:
\begin{lstlisting}
#include <stdio.h>
int main (void)
{
char buffer[100];
scanf ("%s", buffer);
for (int i = 0; buffer[i]; i++)
if (buffer[i] >= 'A' && buffer[i] <= 'M')
buffer[i] += 13;
else if (buffer[i] >= 'M' && buffer[i] <= 'Z')
buffer[i] -= 13;
else if (buffer[i] >= 'a' && buffer[i] <= 'm')
buffer[i] += 13;
else if (buffer[i] >= 'm' && buffer[i] <= 'z')
buffer[i] -= 13;
printf ("%s\n", buffer);
return 0;
}
\end{lstlisting}
Bemerkungen:
\begin{itemize}
\item
Ein String in doppelten Anführungszeichen steht für ein Array
von \lstinline{char}s, also ein Array kleiner Zahlen, das
durch ein Null-Symbol abgeschlossen ist.
Beispiel: \lstinline{"A"} steht für das Array \lstinline|{ 65, 0 }|.
Ein Buchstabe in einfachen Anführungszeichen (Apostrophen)
steht für den Buchstaben selbst, also eine kleine Zahl.
Beispiel: \lstinline{'A'} ist in C dasselbe wie die Zahl
\lstinline{65}.
Unter Verwendung dieser Schreibweise ist es insbesondere nicht
nötig, die Zahlenwerte von Buchstaben in einer ASCII-Tabelle
nachzuschlagen. Im Gegenteil: Dadurch daß man Buchstaben
hinschreibt, ist zum einen sofort klar, was das Programm
macht; zum anderen funktioniert es auch mit anderen
Zeichensätzen als ASCII, sofern diese die Buchstaben in
alphabetischer Reihenfolge enthalten.
\item
Da auf das \lstinline{for} nur eine einzige Anweisung (nämlich
ein langgezogenes \lstinline{if}) folgt, sind hier keine
geschweiften Klammern nötig. Sie wären aber auch nicht falsch.
\item
Die Verwendung eines Puffers fester Größe zum Einlesen eines
Strings mit \lstinline{scanf ("%s", buffer)} ist eigentlich
ein Unding, da hier keine Prüfung auf einen möglichen
Pufferüberlauf stattfindet.
Um diesem Problem zu begegnen, kann man z.\,B.\ in der
Formatspezifikation für \lstinline{scanf()} eine maximale
Feldgröße spezifizieren. Diese bezieht sich auf die Anzahl der
eingelesenen Buchstaben; der Puffer muß zusätzlich Platz für
das Null-Symbol am Ende des Strings bereitstellen. In diesem
Beispiel mit einem Puffer der Länge 100 wäre demnach
\lstinline{scanf ("%99s", buffer)} ein sinnvoller Aufruf.
Eine andere Möglichkeit ist die Verwendung anderer Funktionen zum
Einlesen des Strings, z.\,B.\ \lstinline{fgets (buffer, 100, stdin)}.
(Bei \lstinline{fgets()} steht die Größenangabe für die Größe
des Puffers einschließlich Null-Symbol.)
\end{itemize}
\exercise{Programm analysieren}
Wir betrachten das folgende C-Programm (Datei: \gitfile{hp}{20181022}{aufgabe-2.c}):
\begin{lstlisting}
char*f="char*f=%c%s%c;main(){printf(f,34,f,34,10);}%c";main(){printf(f,34,f,34,10);}
\end{lstlisting}
\vspace{-\medskipamount}
\begin{itemize}
\item[(a)]
Was bewirkt dieses Programm?
\item[(b)]
Wofür stehen die Zahlen?
\item[(c)]
Ergänzen Sie das Programm derart, daß seine \lstinline{main()}-Funktion
\lstinline{int main (void)} lautet und eine \lstinline{return}-Anweisung hat,
wobei die in Aufgabenteil (a) festgestellte Eigenschaft erhalten bleiben soll.
\end{itemize}
\solution
\begin{itemize}
\item[(a)]
\textbf{Was bewirkt dieses Programm?}
Es gibt \emph{seinen eigenen Quelltext\/} aus.
(Wichtig ist die Bezugnahme auf den eigenen Quelltext.
Die Angabe\\
"`Es gibt
\lstinline|char*f="char*f=%c%s%c;main(){printf(f,34,f,34,10);}%c";main(){printf(f,34,f,34,10);}|
aus"'\\
genügt insbesondere nicht.)
\item[(b)]
\textbf{Wofür stehen die Zahlen?}
Die 34 steht für ein Anführungszeichen und die 10 für ein
Zeilenendezeichen (\lstinline{\n}).
Hintergrund: Um den eigenen Quelltext ausgeben zu können, muß
das Programm auch Anführungszeichen und Zeilenendezeichen
ausgeben. Dies geschieht normalerweise mit vorangestelltem
Backslash: \lstinline{\"} bzw.\ \lstinline{\n}. Um dann aber
den Backslash ausgeben zu können, müßte man diesem ebenfalls
einen Backslash voranstellen: \lstinline{\\}. Damit dies nicht
zu einer Endlosschleife wird, verwendet der Programmierer
dieses Programms den Trick mit den Zahlen, die durch
\lstinline{%c} als Zeichen ausgegeben werden.
\item[(c)]
\textbf{Ergänzen Sie das Programm derart, daß seine \lstinline{main()}-Funktion
\lstinline{int main (void)} lautet und eine \lstinline{return}-Anweisung hat,
wobei die in Aufgabenteil (a) festgestellte Eigenschaft erhalten bleiben soll.}
Datei: \gitfile{hp}{20181022}{loesung-2.c}
\begin{lstlisting}[gobble=8]
char*f="char*f=%c%s%c;int main(void){printf(f,34,f,34,10);return 0;}%c";
int main(void){printf(f,34,f,34,10);return 0;}
\end{lstlisting}
Das Programm ist eine einzige, lange Zeile, die hier nur aus
Platzgründen als zwei Zeilen abgedruckt wird. Auf das
Semikolon am Ende der "`ersten Zeile"' folgt unmittelbar -- ohne Leerzeichen --
das Schlüsselwort \lstinline{int} am Anfang der "`zweiten Zeile"'.
Mit "`die in Aufgabenteil (a) festgestellte Eigenschaft"' ist
gemeint, daß das Programm weiterhin seinen eigenen Quelltext
ausgeben soll. Die Herausforderung dieser Aufgabe besteht
darin, das Programm zu modifizieren, ohne diese Eigenschaft zu
verlieren.
Zusatzaufgabe für Interessierte: Ergänzen Sie das Programm so,
daß es auch mit \lstinline[style=cmd]{-Wall} ohne Warnungen
compiliert werden kann.
Hinweis dazu: \lstinline{#include<stdio.h>}
(ohne Leerzeichen, um Platz zu sparen)
Lösung der Zusatzaufgabe: \gitfile{hp}{20181022}{loesung-2x.c}
\end{itemize}
\exercise{Kalender-Berechnung}
Am 3.\,1.\,2009 meldete \emph{heise online\/}:
\begin{quote}
Kunden des ersten mobilen Media-Players von Microsoft
erlebten zum Jahresende eine böse Überraschung:
Am 31.\ Dezember 2008 fielen weltweit alle Zune-Geräte der ersten Generation aus.
Ursache war ein interner Fehler bei der Handhabung von Schaltjahren.
\strut\hfill\url{http://heise.de/-193332},
\end{quote}
Der Artikel verweist auf ein Quelltextfragment (Datei: \gitfile{hp}{20181022}{aufgabe-3.c}),
das für einen gegebenen Wert \lstinline{days}
das Jahr und den Tag innerhalb des Jahres
für den \lstinline{days}-ten Tag nach dem 1.\,1.\,1980 berechnen soll:
\begin{lstlisting}
year = ORIGINYEAR; /* = 1980 */
while (days > 365)
{
if (IsLeapYear (year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}
\end{lstlisting}
\goodbreak
Dieses Quelltextfragment weist mehrere Code-Verdopplungen auf:
\begin{itemize}
\item
Die Anweisung \lstinline{year += 1} taucht an zwei Stellen auf.
\item
Es gibt zwei unabhängige Abfragen \lstinline{days > 365} und \lstinline{days > 366}:\\
eine in einer \lstinline{while}- und die andere in einer \lstinline{if}-Bedingung.
\item
Die Länge eines Jahres wird nicht durch eine Funktion berechnet oder in einer Variablen gespeichert;
stattdessen werden an mehreren Stellen die expliziten numerischen Konstanten 365 und 366 verwendet.
\end{itemize}
Diese Probleme führten am 31.\ Dezember 2008 zu einer Endlosschleife,
die sich -- z.\,B.\ durch eine Funktion \lstinline{DaysInYear()} -- leicht hätte vermeiden lassen.
Gut hingegen ist die Verwendung einer Konstanten \lstinline{ORIGINYEAR}
anstelle der Zahl 1980
sowie die Kapselung der Berechnung der Schaltjahr-Bedingung
in einer Funktion \lstinline{IsLeapYear()}.
\begin{itemize}
\item[(a)]
Erklären Sie das Zustandekommen der Endlosschleife.
\item[(b)]
Schreiben Sie das Quelltextfragment so um, daß es die beschriebenen Probleme
nicht mehr enthält.
\end{itemize}
\solution
\begin{itemize}
\item[(a)]
\textbf{Erklären Sie das Zustandekommen der Endlosschleife.}
Das Programm startet mit demjenigen Wert für \lstinline{days},
der der Anzahl der Tage vom 1.\,1.\,1980 bis zum
31.\,12.\,2008 entspricht. Die \lstinline{while}-Schleife
läuft zunächst solange korrekt durch, bis \lstinline{year} den
Wert \lstinline{2008} und \lstinline{days} den Wert
\lstinline{366} hat. (Der 31.\,12.\ des Schaltjahres 2008 ist
der 366.\ Tag seines Jahres.)
Die Bedingung der \lstinline{while}-Schleife ist damit
weiterhin erfüllt; das Programm läuft weiter.
Da 2008 ein Schaltjahr ist, ist auch die Bedingung der äußeren
\lstinline{if}-Anweisung erfüllt.
Da \lstinline{days} den Wert 366 hat und dieser nicht größer
als 366 ist, ist die innere \lstinline{if}-Bedingung nicht
erfüllt. Somit wird innerhalb der \lstinline{while}-Schleife
kein weiterer Code ausgeführt, die \lstinline{while}-Bedingung
bleibt erfüllt, und das Programm führt eine Endlosschleife
aus.
\item[(b)]
\textbf{Schreiben Sie das Quelltextfragment so um, daß es die beschriebenen Probleme
nicht mehr enthält.}
Um das Programm zu testen, genügt es, das Datum auf den
31.\,12.\,1980 zu stellen, also \lstinline{days} auf den Wert
366 zu setzen. Darüberhinaus muß man die Funktion
\lstinline{IsLeapYear()} bereitstellen (vgl.\ Aufgabe 3 vom
10.\,10.\,2016).
Der Quelltext \gitfile{hp}{20181022}{loesung-3-f1.c} ist eine lauffähige
Version des Programms, die den Fehler (Endlosschleife)
reproduziert.
\breath
Es liegt nahe, den Fehler in der \lstinline{while}-Bedingung
zu korrigieren, so daß diese Schaltjahre berücksichtigt. Der
Quelltext \gitfile{hp}{20181022}{loesung-3-f2.c} behebt den Fehler auf diese
Weise mit Hilfe von Und- (\lstinline{&&}) und
Oder-Verknüpfungen (\lstinline{||}) in der
\lstinline{while}-Bedingung.
Der Quelltext \gitfile{hp}{20181022}{loesung-3-f3.c} vermeidet die umständliche
Formulierung mit \lstinline{&&} und \lstinline{||} durch
Verwendung des ternären Operators \lstinline{?:}. Dieser
stellt eine "`\lstinline{if}-Anweisung für Ausdrücke"' bereit.
In diesem Fall liefert er für die rechte Seite des Vergleichs
\lstinline{days >} den Wert 366 im Falle eines Schaltjahrs
bzw.\ ansonsten den Wert 365.
Beide Lösungen \gitfile{hp}{20181022}{loesung-3-f2.c} und \gitfile{hp}{20181022}{loesung-3-f3.c}
sind jedoch im Sinne der Aufgabenstellung \textbf{falsch}.
Diese lautet: "`Schreiben Sie das Quelltextfragment so um,
daß es die beschriebenen Probleme nicht mehr enthält."'
Mit den beschriebenen Problemen sind die genannten drei
Code-Verdopplungen gemeint, und diese befinden sich weiterhin
im Quelltext. Damit ist der Fehler zwar "`korrigiert"', aber
das Programm ist eher noch unübersichtlicher geworden, so daß
nicht klar ist, ob es nicht noch weitere Fehler enthält.
\breath
Eine richtige Lösung liefert \gitfile{hp}{20181022}{loesung-3-4.c}. Dieses
Programm speichert den Wert der Tage im Jahr in einer
Variablen \lstinline{DaysInYear}. Damit erübrigen sich die
\lstinline{if}-Anweisungen innerhalb der
\lstinline{while}-Schleife, und die damit verbundenen
Code-Verdopplungen verschwinden.
Etwas unschön ist hierbei die neu hinzugekommene
Code-Verdopplung bei der Berechnung von \lstinline{DaysInYear}.
Diese ist allerdings weniger kritisch als die vorherigen, da
sie nur einmal innerhalb der \lstinline{while}-Schleife
vorkommt und das andere Mal außerhalb derselben.
Um diese Code-Verdopplung loszuwerden, kann man das
\lstinline{if} durch den \lstinline{?:}-Operator ersetzen und
die Zuweisung innerhalb der \lstinline{while}-Bedingung
vornehmen -- siehe \gitfile{hp}{20181022}{loesung-3-5.c}. Dies ist einer der
seltenen Fälle, in denen ein Programm \emph{übersichtlicher\/}
wird, wenn eine Zuweisung innerhalb einer Bedingung
stattfindet.
Alternativ kann \lstinline{DaysInYear()} auch eine Funktion
sein -- siehe \gitfile{hp}{20181022}{loesung-3-6.c}. Diese Version ist
wahrscheinlich die übersichtlichste, hat jedoch den Nachteil,
daß die Berechnung von \lstinline{DaysInYear()} zweimal statt
nur einmal pro Schleifendurchlauf erfolgt, wodurch Rechenzeit
verschwendet wird.
\gitfile{hp}{20181022}{loesung-3-7.c} und \gitfile{hp}{20181022}{loesung-3-8.c} beseitigen
dieses Problem durch eine Zuweisung des Funktionsergebnisses
an eine Variable -- einmal innerhalb der
\lstinline{while}-Bedingung und einmal außerhalb.
Der zweimalige Aufruf der Funktion \lstinline{DaysInYear()} in
\gitfile{hp}{20181022}{loesung-3-8.c} zählt nicht als Code-Verdopplung, denn
der Code ist ja in einer Funktion gekapselt. (Genau dazu sind
Funktionen ja da: daß man sie mehrfach aufrufen kann.)
\breath
Fazit: Wenn Sie sich beim Programmieren bei
Cut-And-Paste-Aktionen erwischen, sollten Sie die Struktur
Ihres Programms noch einmal überdenken.
Wahrscheinlich gibt es dann eine elegantere Lösung, deren
Korrektheit man auf den ersten Blick sieht.
\end{itemize}
\end{document}
#include <stdio.h>
int main (void)
{
char buffer[100];
scanf ("%s", buffer);
for (int i = 0; buffer[i]; i++)
if (buffer[i] >= 'A' && buffer[i] <= 'M')
buffer[i] += 13;
else if (buffer[i] >= 'M' && buffer[i] <= 'Z')
buffer[i] -= 13;
else if (buffer[i] >= 'a' && buffer[i] <= 'm')
buffer[i] += 13;
else if (buffer[i] >= 'm' && buffer[i] <= 'z')
buffer[i] -= 13;
printf ("%s\n", buffer);
return 0;
}
char*f="char*f=%c%s%c;int main(void){printf(f,34,f,34,10);return 0;}%c";int main(void){printf(f,34,f,34,10);return 0;}
#include<stdio.h>
char*f="#include<stdio.h>%cchar*f=%c%s%c;int main(void){printf(f,10,34,f,34,10);return 0;}%c";int main(void){printf(f,10,34,f,34,10);return 0;}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
int DaysInYear;
if (IsLeapYear (year))
DaysInYear = 366;
else
DaysInYear = 365;
while (days > DaysInYear)
{
days -= DaysInYear;
year += 1;
if (IsLeapYear (year))
DaysInYear = 366;
else
DaysInYear = 365;
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
int DaysInYear;
while (days > (DaysInYear = IsLeapYear (year) ? 366 : 365))
{
days -= DaysInYear;
year += 1;
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int DaysInYear (int year)
{
if (IsLeapYear (year))
return 366;
else
return 365;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
while (days > DaysInYear (year))
{
days -= DaysInYear (year);
year += 1;
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int DaysInYear (int year)
{
if (IsLeapYear (year))
return 366;
else
return 365;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
int d;
while (days > (d = DaysInYear (year)))
{
days -= d;
year += 1;
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int DaysInYear (int year)
{
if (IsLeapYear (year))
return 366;
else
return 365;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
int d = DaysInYear (year);
while (days > d)
{
days -= d;
year += 1;
d = DaysInYear (year);
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
while (days > 365)
{
if (IsLeapYear (year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
while ((IsLeapYear (year) && days > 366)
|| (!IsLeapYear (year) && days > 365))
{
if (IsLeapYear (year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
#include <stdio.h>
int IsLeapYear (int year)
{
if (year % 4)
return 0;
else if (year % 100)
return 1;
else if (year % 400)
return 0;
else
return 1;
}
int main (void)
{
int ORIGINYEAR = 1980;
int days = 366;
int year;
year = ORIGINYEAR; /* = 1980 */
while (days > (IsLeapYear (year) ? 366 : 365))
{
if (IsLeapYear (year))
{
if (days > 366)
{
days -= 366;
year += 1;
}
}
else
{
days -= 365;
year += 1;
}
}
printf ("year = %d\ndays = %d\n", year, days);
return 0;
}
......@@ -32,6 +32,7 @@ Musterlösungen:
---------------
* [08.10.2018: ](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181008/hp-musterloesung-20181008.pdf)
* [15.10.2018: Fibonacci-Zahlen, fehlerhaftes Programm, "Hello, world!"](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181015/hp-musterloesung-20181015.pdf)
* [22.10.2018: ROT13-Verschlüsselung, Programm analysieren, Kalender-Berechnung](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181022/hp-musterloesung-20181022.pdf)
Tafelbilder:
------------
......
No preview for this file type
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment