diff --git a/20190107/hp-musterloesung-20190107.pdf b/20190107/hp-musterloesung-20190107.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0d00d4a465d18b0a09b9b904da525aba72be609c Binary files /dev/null and b/20190107/hp-musterloesung-20190107.pdf differ diff --git a/20190107/hp-musterloesung-20190107.tex b/20190107/hp-musterloesung-20190107.tex new file mode 100644 index 0000000000000000000000000000000000000000..67c529509f8f46d581459d0de9d641a322e70344 --- /dev/null +++ b/20190107/hp-musterloesung-20190107.tex @@ -0,0 +1,447 @@ +% hp-musterloesung-20190107.pdf - Solutions to the Exercises on Low-Level Programming / Applied Computer Sciences +% Copyright (C) 2013, 2015, 2016, 2017, 2018, 2019 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: Speicherformate von Zahlen, Zeigerarithmetik + +\documentclass[a4paper]{article} + +\usepackage{pgscript} +\usepackage{sfmath} + +\begin{document} + + \section*{Hardwarenahe Programmierung\\ + Musterlösung zu den Übungsaufgaben -- 7.\ Januar 2019} + + \exercise{Speicherformate von Zahlen} + + Wir betrachten das folgende Programm (\gitfile{hp}{20190107}{aufgabe-1.c}): + \begin{lstlisting}[style=numbered] + #include <stdio.h> + #include <stdint.h> + + typedef struct + { + uint32_t a; + uint64_t b; + uint8_t c; + } three_numbers; + + int main (void) + { + three_numbers xyz = { 1819042120, 2410670883059281007, 0 }; + printf ("%s\n", &xyz); + return 0; + } + \end{lstlisting} + + Das Programm wird für einen 32-Bit-Rechner compiliert und ausgeführt.\\ + (Die \lstinline[style=cmd]{gcc}-Option \lstinline[style=cmd]{-m32} sorgt dafür, + daß \lstinline[style=cmd]{gcc} Code für einen 32-Bit-Prozessor erzeugt.) + + \begin{lstlisting}[style=terminal] + $ ¡gcc -Wall -m32 aufgabe-2.c -o aufgabe-2¿ + aufgabe-2.c: In function "main": + aufgabe-2.c:14:13: warning: format "%s" expects argument of type "char *", but + argument 2 has type "three_numbers * {aka struct <anonymous> *}" [-Wformat=] + printf ("%s\n", &xyz); + ^ + $ ¡./aufgabe-2¿ + Hallo, Welt! + \end{lstlisting} + + \begin{enumerate}[\quad(a)] + \item + Erklären Sie die beim Compilieren auftretende Warnung. + \points{2} + \item + Erklären Sie die Ausgabe des Programms. + \points{4} + \item + Welche Endianness hat der verwendete Rechner? + Wie sähe die Ausgabe auf einem Rechner mit entgegengesetzter Endianness aus? + \points{2} + \item + Dasselbe Programm wird nun für einen 64-Bit-Rechner compiliert und ausgeführt.\\ + (Die \lstinline[style=cmd]{gcc}-Option \lstinline[style=cmd]{-m64} sorgt dafür, + daß \lstinline[style=cmd]{gcc} Code für einen 64-Bit-Prozessor erzeugt.) + \begin{lstlisting}[style=terminal,gobble=8] + $ ¡gcc -Wall -m64 aufgabe-2.c -o aufgabe-2¿ + aufgabe-2.c: In function "main": + aufgabe-2.c:14:13: warning: format "%s" expects argument of type "char *", + but argument 2 has type "three_numbers * {aka struct <anonymous> *}" + [-Wformat=] + printf ("%s\n", &xyz); + ^ + $ ¡./aufgabe-2¿ + Hall5V + \end{lstlisting} + (Es ist möglich, daß die konkrete Ausgabe auf Ihrem Rechner anders aussieht.)\par + Erklären Sie die geänderte Ausgabe des Programms. + \points{3} + \end{enumerate} + + \solution + + \begin{enumerate}[\quad(a)] + \item + \textbf{Erklären Sie die beim Compilieren auftretende Warnung.} + + Die Funktion \lstinline{printf()} mit der Formatspezifikation \lstinline{%s} + erwartet als Parameter einen String, d.\,h.\ einen Zeiger auf \lstinline{char}. + Die Adresse (\lstinline{&}) der Variablen \lstinline{xyz} + ist zwar ein Zeiger, aber nicht auf \lstinline{char}, + sondern auf einen \lstinline{struct} vom Typ \lstinline{three_numbers}. + Eine implizite Umwandlung des Zeigertyps ist zwar möglich, + aber normalerweise nicht das, was man beabsichtigt. + + \item + \textbf{Erklären Sie die Ausgabe des Programms.} + + Ein String in C ist ein Array von \lstinline{char}s + bzw.\ ein Zeiger auf \lstinline{char}. + Da die Funktion \lstinline{printf()} mit der Formatspezifikation \lstinline{%s} + einen String erwartet, wird sie das, worauf der übergebene Zeiger zeigt, + als ein Array von \lstinline{char}s interpretieren. + Ein \lstinline{char} entspricht einer 8-Bit-Speicherzelle. + Um die Ausgabe des Programms zu erklären, müssen wir daher + die Speicherung der Zahlen in den einzelnen 8-Bit-Speicherzellen betrachten. + + Hierfür wandeln wir zunächst die Zahlen von Dezimal nach Hexadezimal um. + Sofern nötig (hier nicht der Fall) füllen wir von links mit Nullen auf, + um den gesamten von der Variablen belegten Speicherplatz zu füllen + (hier: 32 Bit, 64 Bit, 8 Bit). + Jeweils 2 Hex-Ziffern stehen für 8 Bit. + \begin{center} + \begin{tabular}{rcl} + dezimal & & hexadezimal \\[\smallskipamount] + 1\,819\,042\,120 & = & 6C\,6C\,61\,48 \\ + 2\,410\,670\,883\,059\,281\,007 & = & 21\,74\,6C\,65\,57\,20\,2C\,6F \\ + 0 & = & 00 + \end{tabular} + \end{center} + Die Anordnung dieser 8-Bit-Zellen im Speicher lautet + \textbf{auf einem Big-Endian-Rechner} wie folgt: + \begin{center} + \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|}\hline + \raisebox{0.5ex}{\strut} + 6C & 6C & 61 & 48 & + 21 & 74 & 6C & 65 & 57 & 20 & 2C & 6F & + 00 + \\\hline + \end{tabular}\\[-4.2ex] + \kern1.7em% + $\underbrace{\rule{9.8em}{0pt}}_{\mbox{\lstinline{a}}}$\kern1pt% + $\underbrace{\rule{18.8em}{0pt}}_{\mbox{\lstinline{b}}}$\kern1pt% + $\underbrace{\rule{2.2em}{0pt}}_{\mbox{\lstinline{c}}}$% + \end{center} + \textbf{Auf einem Little-Endian-Rechner} lautet sie hingegen: + \begin{center} + \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|}\hline +% \raisebox{0.5ex}{\strut} +% H & a & l & l & +% o & , & & W & e & l & t & ! & \\\hline + \raisebox{0.5ex}{\strut} + 48 & 61 & 6C & 6C & + 6F & 2C & 20 & 57 & 65 & 6C & 74 & 21 & + 00 + \\\hline + \end{tabular}\\[-4.2ex] + \kern1.7em% + $\underbrace{\rule{9.8em}{0pt}}_{\mbox{\lstinline{a}}}$\kern1pt% + $\underbrace{\rule{18.8em}{0pt}}_{\mbox{\lstinline{b}}}$\kern1pt% + $\underbrace{\rule{2.2em}{0pt}}_{\mbox{\lstinline{c}}}$% + \end{center} + Anhand einer ASCII-Tabelle erkennt man, + daß die Big-Endian-Variante dem String \lstinline{"llaH!tleW ,o"} + und die Little-Endian-Variante dem String \lstinline{"Hallo, Welt!"} + entspricht -- jeweils mit einem Null-Symbol am Ende, + das von der Variablen \lstinline{c} herrührt. + + Auf einem Little-Endian-Rechner wird daher + \lstinline[style=terminal]{Hallo, Welt!} ausgegeben. + + \item + \textbf{Welche Endianness hat der verwendete Rechner?} + + Little-Endian (Begründung siehe oben) + + \textbf{Wie sähe die Ausgabe auf einem Rechner mit entgegengesetzter Endianness aus?} + + \lstinline[style=terminal]{llaH!tleW ,o} (Begründung siehe oben) + + \item + \textbf{Dasselbe Programm wird nun für einen 64-Bit-Rechner compiliert und ausgeführt.\\ + (Die \lstinline[style=cmd]{gcc}-Option \lstinline[style=cmd]{-m64} sorgt dafür, + daß \lstinline[style=cmd]{gcc} Code für einen 64-Bit-Prozessor erzeugt.)} + \begin{lstlisting}[style=terminal,gobble=8] + $ ¡gcc -Wall -m64 aufgabe-2.c -o aufgabe-2¿ + aufgabe-2.c: In function "main": + aufgabe-2.c:14:13: warning: format "%s" expects argument of type "char *", + but argument 2 has type "three_numbers * {aka struct <anonymous> *}" + [-Wformat=] + printf ("%s\n", &xyz); + ^ + $ ¡./aufgabe-2¿ + Hall5V + \end{lstlisting} + \textbf{(Es ist möglich, daß die konkrete Ausgabe auf Ihrem Rechner anders aussieht.)}\par + \textbf{Erklären Sie die geänderte Ausgabe des Programms.} + + \goodbreak + + Auf einem 64-Bit-Rechner hat eine 64-Bit-Variable + ein \textbf{64-Bit-Alignment}, + d.\,h.\ ihre Speicheradresse muß durch 8 teilbar sein. + + Der Compiler legt die Variablen daher wie folgt im Speicher ab (Little-Endian): + \begin{center} + \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}\hline + \raisebox{0.5ex}{\strut} + 48 & 61 & 6C & 6C & ?? & ?? & ?? & ?? & + 6F & 2C & 20 & 57 & 65 & 6C & 74 & 21 & + 00 + \\\hline + \end{tabular}\\[-4.2ex] + \kern1.7em% + $\underbrace{\rule{9.8em}{0pt}}_{\mbox{\lstinline{a}}}$\kern1pt% + $\underbrace{\rule{9.1em}{0pt}}_{\mbox{Füll-Bytes}}$\kern1pt% + $\underbrace{\rule{18.8em}{0pt}}_{\mbox{\lstinline{b}}}$\kern1pt% + $\underbrace{\rule{2.2em}{0pt}}_{\mbox{\lstinline{c}}}$% + \end{center} + Der Inhalt der Füll-Bytes ist undefiniert. + Im Beispiel aus der Aufgabenstellung entsteht hier die Ausgabe + \lstinline[style=terminal]{5V}, was den (zufälligen) hexadezimalen Werten + 35 56 entspricht: + \begin{center} + \begin{tabular}{|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|c|}\hline + \raisebox{0.5ex}{\strut} + 48 & 61 & 6C & 6C & 35 & 56 & 00 & ?? & + 6F & 2C & 20 & 57 & 65 & 6C & 74 & 21 & + 00 + \\\hline + \end{tabular}\\[-4.2ex] + \kern1.7em% + $\underbrace{\rule{9.8em}{0pt}}_{\mbox{\lstinline{a}}}$\kern1pt% + $\underbrace{\rule{9.1em}{0pt}}_{\mbox{Füll-Bytes}}$\kern1pt% + $\underbrace{\rule{18.8em}{0pt}}_{\mbox{\lstinline{b}}}$\kern1pt% + $\underbrace{\rule{2.2em}{0pt}}_{\mbox{\lstinline{c}}}$% + \end{center} + Da danach die Aufgabe aufhört, muß an der nächsten Stelle + ein Null-Symbol stehen, das das Ende des Strings anzeigt. + Der Inhalt der darauf folgenden Speicherzelle bleibt unbekannt. + \end{enumerate} + + \vspace{4cm} + \goodbreak + + \exercise{Zeigerarithmetik} + + Wir betrachten das folgende Programm (\gitfile{hp}{20190107}{aufgabe-2.c}): + \begin{lstlisting} + #include <stdio.h> + #include <stdint.h> + + void output (uint16_t *a) + { + for (int i = 0; a[i]; i++) + printf (" %d", a[i]); + printf ("\n"); + } + + int main (void) + { + uint16_t prime_numbers[] = { 2, 3, 5, 7, 11, 13, 17, 0 }; + + uint16_t *p1 = prime_numbers; + output (p1); + p1++; + output (p1); + + char *p2 = prime_numbers; + output (p2); + p2++; + output (p2); + + return 0; + } + \end{lstlisting} + + \goodbreak + + Das Programm wird compiliert und ausgeführt: + + \begin{lstlisting}[style=terminal] + $ ¡gcc -Wall aufgabe-2.c -o aufgabe-2¿ + aufgabe-2.c: In function 'main': + aufgabe-2.c:20:13: warning: initialization from + incompatible pointer type [enabled by default] + aufgabe-2.c:21:3: warning: passing argument 1 of 'output' from + incompatible pointer type [enabled by default] + aufgabe-2.c:4:6: note: expected 'uint16_t *' but argument is of type 'char *' + aufgabe-2.c:23:3: warning: passing argument 1 of 'output' from + incompatible pointer type [enabled by default] + aufgabe-2.c:4:6: note: expected 'uint16_t *' but argument is of type 'char *' + $ ¡./aufgabe-2¿ + 2 3 5 7 11 13 17 + 3 5 7 11 13 17 + 2 3 5 7 11 13 17 + 768 1280 1792 2816 3328 4352 + \end{lstlisting} + + \begin{enumerate}[\quad(a)] + \item + Erklären Sie die Funktionsweise der Funktion \lstinline{output ()}. + \points{2} + \item + Begründen Sie den Unterschied zwischen der ersten (\lstinline{2 3 5 7 11 13 17})\\ + und der zweiten Zeile (\lstinline{3 5 7 11 13 17}) der Ausgabe des Programms. + \points{2} + \item + Erklären Sie die beim Compilieren auftretenden Warnungen\\ + und die dritte Zeile (\lstinline{2 3 5 7 11 13 17}) der Ausgabe des Programms. + \points{3} + \item + Erklären Sie die vierte Zeile (\lstinline{768 1280 1792 2816 3328 4352}) + der Ausgabe des Programms.\\ +% Welche Endianness hat der verwendete Rechner?\\ +% Wie sähe die Ausgabezeile bei umgekehrter Endianness aus? +% +% 2 0 3 0 5 0 7 0 11 --> 2 3 5 7 11 +% 0 3 0 5 0 7 0 11 --> 768 1280 ... +% +% 0 2 0 3 0 5 0 7 0 11 --> 2 3 5 7 11 +% 2 0 3 0 5 0 7 0 11 --> 768 1280 ... +% +% --> Endianness nicht erkennbar! +% + Sie dürfen einen Little-Endian-Rechner voraussetzen. + \points{4} + \end{enumerate} + + \vspace{1cm} + \goodbreak + \solution + + \begin{enumerate}[\quad(a)] + \item + \textbf{Erklären Sie die Funktionsweise der Funktion \lstinline{output ()}.} + + Die Funktion bekommt ein Array von ganzen Zahlen als Zeiger übergeben + und gibt dessen Inhalt auf den Bildschirm aus, + \textbf{bis es auf die Zahl 0 stößt}. + (Die Ende-Markierung 0 wird nicht mit ausgegeben.) + + (Die Erwähnung der Abbruchbedingung der Schleife + ("`bis es auf die Zahl 0 stößt"') + ist ein wichtiger Bestandteil der richtigen Lösung. + Wenn man nämlich einen Zeiger auf ein Array übergibt, + das am Ende keine 0 enthält, + liest die Funktion über das Array hinaus + zufällige Werte aus dem Speicher. + Dies kann zu einem Absturz führen.) + + \item + \textbf{Begründen Sie den Unterschied zwischen der ersten (\lstinline{2 3 5 7 11 13 17})\\ + und der zweiten Zeile (\lstinline{3 5 7 11 13 17}) der Ausgabe des Programms.} + + Zwischen der Ausgabe der ersten und der zweiten Zeile + wurde der Zeiger \lstinline{p1} um 1 inkrementiert; + er zeigt danach auf die nächste ganze Zahl im Array + (also die zweite Zahl im Array -- mit Index 1 statt 0). + Als Folge davon wird beim zweiten Aufruf von \lstinline{output()} + die erste Zahl des Arrays nicht mehr mit ausgegeben. + + \item + \textbf{Erklären Sie die beim Compilieren auftretenden Warnungen\\ + und die dritte Zeile (\lstinline{2 3 5 7 11 13 17}) der Ausgabe des Programms.} + + Die Warnungen kommen daher, + daß die Variable \lstinline{p2} ein Zeiger auf \lstinline{char}-Variable ist, + ihr jedoch ein anderer Zeigertyp, + nämlich ein Zeiger auf \lstinline{uint16_t}-Variable, zugewiesen wird. + Beim Aufruf der Funktion \lstinline{output()} + wird umgekehrt dem Parameter \lstinline{a}, + der auf \lstinline{uint16_t}-Variable zeigt, + ein Zeiger auf \lstinline{char} zugewiesen, + was dieselbe Warnung hervorruft. + + Trotz des anderen Zeigertyps und trotz der Warnungen + zeigt \lstinline{p2} auf die Speicheradresse + der ersten Zahl im Array, + so daß die Funktion \lstinline{output()} normal arbeitet. + + \item + \textbf{Erklären Sie die vierte Zeile (\lstinline{768 1280 1792 2816 3328 4352}) + der Ausgabe des Programms.\\ + Sie dürfen einen Little-Endian-Rechner voraussetzen.} + + Zwischen der Ausgabe der ersten und der zweiten Zeile + wurde der Zeiger \lstinline{p2} um 1 inkrementiert. + Da \lstinline{p2} auf \lstinline{char}-Variable zeigt + und nicht auf \lstinline{uint16_t}-Variable, + zeigt er danach \emph{nicht\/} auf die nächste ganze Zahl im Array, + sondern auf die \emph{nächste Speicherzelle}. + Da eine \lstinline{uint16_t}-Variable \emph{zwei\/} Speicherzellen belegt, + zeigt \lstinline{p2} nach dem Inkrementieren + \emph{auf das zweite Byte\/} der ersten Variablen im Array. + Die Funktion \lstinline{output()} + liest immer ganze \lstinline{uint16_t}-Variablen + und nimmt für die Ausgabe noch das erste Byte der zweiten Zahl im Array hinzu. + + Auf einem Little-Endian-Rechner hat das zweite Byte der Zahl 2 den Wert 0 + und das erste Byte der Zahl 3 den Wert 3. + Wenn man dies zu einer Little-Endian-16-Bit-Zahl zusammensetzt, + entsteht die Zahl $256\cdot 3 + 0 = 768$. + Entsprechendes gilt für alle anderen Zahlen im Array. + + \begin{center} + $\overbrace{\rule{3.4em}{0pt}}^{\mbox{\lstinline{2}}}$\kern1pt% + $\overbrace{\rule{3.4em}{0pt}}^{\mbox{\lstinline{3}}}$\kern1pt% + $\overbrace{\rule{3.4em}{0pt}}^{\mbox{\lstinline{5}}}$\kern1pt% + $\overbrace{\rule{3.4em}{0pt}}^{\mbox{\lstinline{7}}}$\kern1pt% + \raisebox{2ex}{\dots}\kern-7pt\\ + \begin{tabular}{|c|c|c|c|c|c|c|c|}\hline + \raisebox{0.5ex}{\strut}2 & 0 & 3 & 0 & 5 & 0 & 7 & \dots\\\hline + \end{tabular}\\[-4.2ex] + \kern1.7em% + $\underbrace{\rule{3.4em}{0pt}}_{\mbox{\lstinline{768}}}$\kern1pt% + $\underbrace{\rule{3.4em}{0pt}}_{\mbox{\lstinline{1280}}}$\kern1pt% + $\underbrace{\rule{3.4em}{0pt}}_{\mbox{\lstinline{1792}}}$\kern1pt% + \raisebox{-3ex}{\dots}\kern-7pt + \end{center} + + Die Schleife in der Funktion \lstinline{output()} bricht ab, + sobald sie auf den Zahlenwert 0 trifft. + Dies ist jetzt nur noch zufällig der Fall; + anscheinend hat die Speicherzelle hinter dem Array zufällig den Wert 0. + Ansonsten wäre es auch möglich gewesen, + daß die Schleife über das Array hinaus immer weiter liest, + was zu einem Absturz führen kann. + + \textbf{Zusatzübung:} + Auf einem Big-Endian-Rechner verhält sich das Programm genauso. + Versuchen Sie, auch dies zu erklären. + + \end{enumerate} + +\end{document} diff --git a/README.md b/README.md index 0eba719ef3be6a360e568137241ffd1512b997d0..dc661ab9e520685db5999b566ac4e7154858dc06 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,9 @@ Musterlösungen: * [19.11.2018: Arrays mit Zahlen, hüpfender Ball](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181119/hp-musterloesung-20181119.pdf) * [26.11.2018: Zahlensysteme, Mikrocontroller](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181126/hp-musterloesung-20181126.pdf) * [03.12.2018: XBM-Grafik, LED-Blinkmuster](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181203/hp-musterloesung-20181203.pdf) - * [10.12.2018: Trickprogrammierung, Thermometer-Baustein an I²C-Bus](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181210/hp-musterloesung-20181203.pdf) + * [10.12.2018: Trickprogrammierung, Thermometer-Baustein an I²C-Bus](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181210/hp-musterloesung-20181210.pdf) * [17.12.2018: Fakultät, Lauflicht, Länge von Strings (Neuauflage)](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20181217/hp-musterloesung-20181217.pdf) + * [07.01.2019: Speicherformate von Zahlen, Zeigerarithmetik](https://gitlab.cvh-server.de/pgerwinski/hp/raw/master/20190107/hp-musterloesung-20190107.pdf) Tafelbilder: ------------ diff --git a/hp-slides-2018ws.pdf b/hp-slides-2018ws.pdf index b4f541b3dc39f9f86438dd39f51262aa1a490099..41345a1d641bf19e544a9b2ef9ea50eed8f0d41a 100644 Binary files a/hp-slides-2018ws.pdf and b/hp-slides-2018ws.pdf differ