Die Sensorbox im Klassenraum
AFB I–IIDie Informatik-AG baut eine Sensorbox, die im Raum B die Temperatur und den CO2-Gehalt der Luft misst. Luca möchte wissen, wie die Messwerte im Arbeitsspeicher liegen. Er schreibt ein kleines Testprogramm (%zu ist die passende Formatangabe für Werte von sizeof) und startet es zweimal hintereinander.
#include <stdio.h> int main(void) { char raum = 'B'; int co2 = 812; // CO2-Gehalt in ppm double temp = 21.5; // Temperatur in °C printf("Groessen: %zu %zu %zu\n", sizeof(raum), sizeof(co2), sizeof(temp)); printf("raum: %p\n", &raum); printf("co2: %p\n", &co2); printf("temp: %p\n", &temp); return 0; }
$ ./sensorbox Groessen: 1 4 8 raum: 0x7fff2078549b co2: 0x7fff2078549c temp: 0x7fff207854a0 $ ./sensorbox Groessen: 1 4 8 raum: 0x7fffc795752b co2: 0x7fffc795752c temp: 0x7fffc7957530
Für den Unterricht hat Luca die Lage der drei Variablen mit vereinfachten Adressen skizziert (siehe Abbildung). Später soll die Box einen Schultag lang (7 Stunden) alle 10 Minuten einen Temperatur- und einen CO2-Wert speichern. Auf dem Mikrocontroller der Box stehen dafür 2048 Byte zur Verfügung.
- Lesen Sie aus der Skizze die Adressen
&raum,&co2und&temp, die Anzahl der Bytes jeder Variable und die Adresse des letzten Bytes vontempab. - Berechnen Sie den Speicherbedarf der Messwerte für einen Schultag und für eine Schulwoche (5 Tage), wenn die Größen aus dem Testlauf gelten. Prüfen Sie, ob eine Woche in die 2048 Byte passt, und berechnen Sie den Bedarf erneut für den Fall, dass die Temperatur als
float(4 Byte) gespeichert wird. - Interpretieren Sie die Ausgabe der beiden Programmstarts: Was ändert sich, was bleibt gleich, und was bedeutet das?
Hinweise
Hinweis zu Aufgabe a)
Hinweis zu Aufgabe b)
double und einen int.Hinweis zu Aufgabe c)
co2 und temp jeweils von raum entfernt? Vergleichen Sie diese Abstände mit den Größen aus der ersten Zeile.„Interpretieren“ heißt: die Zahlen auf die Sache beziehen und in Worten sagen, was sie bedeuten.Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
| Variable | Adresse | Bytes | belegt |
|---|---|---|---|
raum | 0x2003 | 1 | 0x2003 |
co2 | 0x2004 | 4 | 0x2004 bis 0x2007 |
temp | 0x2008 | 8 | 0x2008 bis 0x200F |
Das letzte Byte von temp liegt bei 0x200F (0x2008 + 7). Die Adresse einer Variable ist immer die ihres ersten Bytes.
Erwartungshorizont zu Aufgabe b)
Messzeitpunkte: 7 h · 6 pro Stunde = 42 pro Tag. Pro Zeitpunkt: 8 Byte (double) + 4 Byte (int) = 12 Byte.
- Tag: 42 · 12 Byte = 504 Byte
- Woche: 5 · 504 Byte = 2520 Byte > 2048 Byte — passt nicht.
- mit
float: 4 + 4 = 8 Byte pro Zeitpunkt; Tag 42 · 8 = 336 Byte, Woche 5 · 336 = 1680 Byte ≤ 2048 Byte — passt.
Die Größen 1, 4 und 8 Byte sind typische Werte (so auch im Testlauf); auf anderen Systemen können sie abweichen, deshalb nutzt man sizeof.
Erwartungshorizont zu Aufgabe c)
- Gleich bleiben die Größen (1, 4, 8 Byte) und die Abstände:
co2liegt 1 Byte hinterraum(…49b → …49c),temp4 Byte hinterco2(…49c + 4 = …4a0). Die Variablen liegen hier also direkt hintereinander, jede so groß wie ihr Typ. - Anders sind die Adressen selbst (…2078549b gegenüber …c795752b). Das Betriebssystem legt den Speicherbereich eines Programms bei jedem Start neu fest; man kann sich also nicht auf eine bestimmte Adresse verlassen.
- Die Adressen werden hexadezimal mit
%pausgegeben; Lucas Skizze mit 0x2000 ist eine Vereinfachung dieser langen Adressen.
Vollständig ist die Antwort mit beiden Beobachtungen, einer Hex-Rechnung als Beleg und der Deutung „Adressen ändern sich bei jedem Start, die Lage untereinander und die Größen nicht“.
Der Vokabeltrainer stürzt ab
AFB II–IIILea und Ben programmieren einen Vokabeltrainer für den Französischunterricht. Nach einer Runde mit 20 Vokabeln soll man eingeben, wie viele man richtig hatte, und das Programm gibt die Trefferquote aus. Ben hat den ersten Entwurf geschrieben:
#include <stdio.h> int main(void) { int richtig = 0; int gesamt = 20; printf("Richtige Antworten: "); scanf("%d", richtig); printf("Quote: %.1f %%\n", 100.0 * richtig / gesamt); return 0; }
$ gcc -Wall vokabel.c -o vokabel
vokabel.c: In function 'main':
vokabel.c:8:13: warning: format '%d' expects argument of type 'int *', but argument 2 has type 'int' [-Wformat=]
8 | scanf("%d", richtig);
| ~^ ~~~~~~~
| | |
| | int
| int *
$ ./vokabel
Richtige Antworten: 17
Segmentation faultBeim Nachdenken über den Absturz stellt Lea eine Behauptung auf: „Variablen liegen im Speicher immer in der Reihenfolge, in der sie deklariert sind — die zuerst deklarierte hat die kleinste Adresse, und dahinter kommen die anderen lückenlos.“
- Werten Sie die Warnung des Compilers und das Verhalten beim Programmstart aus, und korrigieren Sie das Programm. Notieren Sie auch die richtige Ausgabe für die Eingabe 17.
- Entwickeln Sie ein C-Programm, mit dem man Leas Behauptung experimentell prüfen kann. Legen Sie fest, woran man in der Ausgabe erkennt, ob die Behauptung zutrifft.
- Ben führt ein solches Testprogramm mit den Variablen
char a,int b,char c,double d(in dieser Reihenfolge deklariert) aus und erhält:Widerlegen Sie Leas Behauptung mithilfe dieser Ausgabe.Konsole$ ./adresstest a: 0x7ffdfd617eda (1 Byte) b: 0x7ffdfd617edc (4 Byte) c: 0x7ffdfd617edb (1 Byte) d: 0x7ffdfd617ee0 (8 Byte)
Hinweise
Hinweis zu Aufgabe a)
%d bei scanf laut Warnung (int *), und was bekommt es stattdessen? Welchen Wert hat richtig in diesem Moment, und was macht scanf mit diesem Wert?Hinweis zu Aufgabe b)
%p) und Größe (sizeof) aus. Was müsste für jede Variable gelten, wenn Lea recht hätte?„Entwickeln“ heißt: ein Vorgehen selbst entwerfen und die Schritte begründen — hier ein Testprogramm plus Auswertungsregel.Hinweis zu Aufgabe c)
Erwartungshorizont
Erwartungshorizont zu Aufgabe a)
Die Warnung ist ernst zu nehmen: scanf muss den eingelesenen Wert in die Variable schreiben und braucht dafür deren Adresse (Typ int *). Ben übergibt aber den Wert von richtig, also 0. scanf behandelt diese 0 als Adresse und versucht, die 17 an Adresse 0 zu schreiben. Dieser Speicher gehört nicht zum Programm, das Betriebssystem bricht ab: „Segmentation fault“ (Speicherzugriffsfehler). Die Warnung beim Übersetzen hat den Fehler also schon angekündigt.
#include <stdio.h> int main(void) { int richtig = 0; int gesamt = 20; printf("Richtige Antworten: "); scanf("%d", &richtig); // Adresse von richtig übergeben printf("Quote: %.1f %%\n", 100.0 * richtig / gesamt); return 0; }
$ ./vokabel Richtige Antworten: 17 Quote: 85.0 %
Rechnung: 100,0 · 17 / 20 = 85,0.
Erwartungshorizont zu Aufgabe b)
#include <stdio.h> int main(void) { char a = 'x'; // bewusst gemischte Typen int b = 1; char c = 'y'; double d = 2.5; printf("a: %p (%zu Byte)\n", &a, sizeof(a)); printf("b: %p (%zu Byte)\n", &b, sizeof(b)); printf("c: %p (%zu Byte)\n", &c, sizeof(c)); printf("d: %p (%zu Byte)\n", &d, sizeof(d)); return 0; }
Auswertung: Die Variablen nach Adressen sortieren. Lea hätte recht, wenn (1) die Reihenfolge der Adressen der Reihenfolge der Deklaration entspricht und (2) jede Adresse genau um die Größe der vorherigen Variable größer ist als deren Adresse (keine Lücken). Sinnvoll sind gemischte Typen, weil der Compiler Variablen oft so anordnet, dass z. B. int und double an „glatten“ Adressen beginnen. Das Programm sollte mehrmals gestartet werden: Die Adressen ändern sich, das Muster nicht.
Erwartungshorizont zu Aufgabe c)
| Adresse (Ende) | Variable | deklariert als |
|---|---|---|
| …eda | a (1 Byte) | 1. |
| …edb | c (1 Byte) | 3. |
| …edc bis …edf | b (4 Byte) | 2. |
| …ee0 bis …ee7 | d (8 Byte) | 4. |
c wurde nach b deklariert, liegt aber an einer kleineren Adresse (…edb < …edc). Damit ist die Behauptung „immer in der Reihenfolge der Deklaration“ durch ein Gegenbeispiel widerlegt. Der Compiler darf die Variablen frei anordnen; hier hat er die beiden char zusammengelegt, sodass b an einer durch 4 teilbaren Adresse beginnt. (Lückenlos sind sie in diesem Lauf zufällig trotzdem — das ändert nichts am Gegenbeispiel.)
Vollständig ist die Antwort mit dem konkreten Adressvergleich und dem Hinweis, dass ein Gegenbeispiel genügt.
