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

Musterlösung 7.1.2019

parent c1b9b259
No related branches found
No related tags found
No related merge requests found
File added
% 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}
......@@ -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:
------------
......
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